forked from MapComplete/MapComplete
		
	Add search previews on the map
This commit is contained in:
		
							parent
							
								
									1c46a65c84
								
							
						
					
					
						commit
						4f52483a98
					
				
					 19 changed files with 315 additions and 87 deletions
				
			
		|  | @ -356,9 +356,32 @@ | ||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "#": "ignore-image-in-then", | ||||||
|  |           "if": "osm_id~*", | ||||||
|  |           "then": { | ||||||
|  |             "special": { | ||||||
|  |               "type": "link", | ||||||
|  |               "text": "<img alt='on osm' textmode='🗺️' src='./assets/svg/osm-logo-us.svg'/>", | ||||||
|  |               "href": "https://www.openstreetmap.org/{osm_id}", | ||||||
|  |               "arialabel": { | ||||||
|  |                 "en": "Open on openstreetmap.org", | ||||||
|  |                 "nl": "Bekijk op openstreetmap.org", | ||||||
|  |                 "de": "Auf openstreetmap.org öffnen", | ||||||
|  |                 "pl": "Otwórz na openstreetmap.org", | ||||||
|  |                 "da": "Åbn på openstreetmap.org" | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|       ], |       ], | ||||||
|       "condition": "id~(node|way|relation)/[0-9]*" |       "condition": { | ||||||
|  |         "or": [ | ||||||
|  |           "id~(node|way|relation)/[0-9]*", | ||||||
|  |           "osm_id~*" | ||||||
|  |         ] | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "id": "rating", |       "id": "rating", | ||||||
|  |  | ||||||
							
								
								
									
										66
									
								
								assets/layers/search/search.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								assets/layers/search/search.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | { | ||||||
|  |   "id": "search", | ||||||
|  |   "description": { | ||||||
|  |     "en": "Priviliged layer showing the search results" | ||||||
|  |   }, | ||||||
|  |   "source": "special", | ||||||
|  |   "title": "{display_name}", | ||||||
|  |   "tagRenderings": [ | ||||||
|  |     { | ||||||
|  |       "id": "intro", | ||||||
|  |       "render": { | ||||||
|  |         "en": "Search result" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "id": "osm", | ||||||
|  |       "render": { | ||||||
|  |         "*": "<a href='https://openstreetmap.org/{osm_type}/{osm_id}'>On OpenStreetMap</a>" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "all_tags" | ||||||
|  |   ], | ||||||
|  |   "pointRendering": [ | ||||||
|  |     { | ||||||
|  |       "location": [ | ||||||
|  |         "point", | ||||||
|  |         "centroid" | ||||||
|  |       ], | ||||||
|  |       "marker": [ | ||||||
|  |         { | ||||||
|  |           "icon": "circle", | ||||||
|  |           "color": "white" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           "icon": { | ||||||
|  |             "render": "globe_alt", | ||||||
|  |             "mappings": [ | ||||||
|  |               { | ||||||
|  |                 "if": "category~city|locality|county", | ||||||
|  |                 "then": "building_office_2" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "if": "category=train_station", | ||||||
|  |                 "then": "train" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "if": "category=airport", | ||||||
|  |                 "then": "airport" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "if": "category=house", | ||||||
|  |                 "then": "house" | ||||||
|  |               }, | ||||||
|  |               { | ||||||
|  |                 "if": "category=shop", | ||||||
|  |                 "then": "building_storefront" | ||||||
|  |               } | ||||||
|  |             ] | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |       "label": "{display_name}", | ||||||
|  |       "labelCssClasses": "bg-white rounded p-2 no-wrap" | ||||||
|  |     } | ||||||
|  |   ] | ||||||
|  | } | ||||||
|  | @ -89,7 +89,7 @@ | ||||||
|     "generate:contributor-list": "vite-node scripts/generateContributors.ts", |     "generate:contributor-list": "vite-node scripts/generateContributors.ts", | ||||||
|     "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", |     "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", | ||||||
|     "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview", |     "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview", | ||||||
|     "prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json", |     "prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json && echo '{}' > ./src/assets/generated/layers/search.json", | ||||||
|     "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", |     "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", | ||||||
|     "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", |     "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", | ||||||
|     "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", |     "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", | ||||||
|  |  | ||||||
|  | @ -21,7 +21,7 @@ export default class TitleHandler { | ||||||
|                 if (selected === undefined) { |                 if (selected === undefined) { | ||||||
|                     return defaultTitle |                     return defaultTitle | ||||||
|                 } |                 } | ||||||
|                 const layer = state.layout.getMatchingLayer(selected.properties) |                 const layer = state.getMatchingLayer(selected.properties) | ||||||
|                 if (layer === undefined) { |                 if (layer === undefined) { | ||||||
|                     return defaultTitle |                     return defaultTitle | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
							
								
								
									
										41
									
								
								src/Logic/Geocoding/GeocodingFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/Logic/Geocoding/GeocodingFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,41 @@ | ||||||
|  | import { GeoCodeResult } from "./GeocodingProvider" | ||||||
|  | import { Store } from "../UIEventSource" | ||||||
|  | import { FeatureSource } from "../FeatureSource/FeatureSource" | ||||||
|  | import { Feature, Geometry } from "geojson" | ||||||
|  | 
 | ||||||
|  | export default class GeocodingFeatureSource implements FeatureSource { | ||||||
|  |     public features: Store<Feature<Geometry, Record<string, string>>[]> | ||||||
|  | 
 | ||||||
|  |     constructor(provider: Store<GeoCodeResult[]>) { | ||||||
|  |         this.features = provider.mapD(geocoded => { | ||||||
|  |             const features: Feature[] = [] | ||||||
|  | 
 | ||||||
|  |             for (const gc of geocoded) { | ||||||
|  |                 if (gc.lat === undefined || gc.lon === undefined) { | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 features.push({ | ||||||
|  |                     type: "Feature", | ||||||
|  |                     properties: { | ||||||
|  |                         id: "search_result_" + gc.osm_type + "/" + gc.osm_id, | ||||||
|  |                         category: gc.category, | ||||||
|  |                         description: gc.description, | ||||||
|  |                         display_name: gc.display_name, | ||||||
|  |                         osm_id: gc.osm_type + "/" + gc.osm_id, | ||||||
|  |                         osm_key: gc.feature?.properties?.osm_key, | ||||||
|  |                         osm_value: gc.feature?.properties?.osm_value | ||||||
|  |                     }, | ||||||
|  |                     geometry: { | ||||||
|  |                         type: "Point", | ||||||
|  |                         coordinates: [gc.lon, gc.lat] | ||||||
|  |                     } | ||||||
|  |                 }) | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return features | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -2,8 +2,10 @@ import { BBox } from "../BBox" | ||||||
| import { Feature, Geometry } from "geojson" | import { Feature, Geometry } from "geojson" | ||||||
| import { DefaultPinIcon } from "../../Models/Constants" | import { DefaultPinIcon } from "../../Models/Constants" | ||||||
| import { Store } from "../UIEventSource" | import { Store } from "../UIEventSource" | ||||||
| 
 | import * as search from "../../assets/generated/layers/search.json" | ||||||
| export type GeocodingCategory = "coordinate" | "city" | "house" | "street" | "locality" | "country" | "train_station" | "county" | "airport" | import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|  | import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
|  | export type GeocodingCategory = "coordinate" | "city" | "house" | "street" | "locality" | "country" | "train_station" | "county" | "airport" | "shop" | ||||||
| 
 | 
 | ||||||
| export type GeoCodeResult = { | export type GeoCodeResult = { | ||||||
|     /** |     /** | ||||||
|  | @ -66,6 +68,8 @@ export interface ReverseGeocodingProvider { | ||||||
| 
 | 
 | ||||||
| export class GeocodingUtils { | export class GeocodingUtils { | ||||||
| 
 | 
 | ||||||
|  |     public static searchLayer=  new LayerConfig(<LayerConfigJson> search, "search") | ||||||
|  | 
 | ||||||
|     public static categoryToZoomLevel: Record<GeocodingCategory, number> = { |     public static categoryToZoomLevel: Record<GeocodingCategory, number> = { | ||||||
|         city: 12, |         city: 12, | ||||||
|         county: 10, |         county: 10, | ||||||
|  | @ -75,7 +79,8 @@ export class GeocodingUtils { | ||||||
|         locality: 14, |         locality: 14, | ||||||
|         street: 15, |         street: 15, | ||||||
|         train_station: 14, |         train_station: 14, | ||||||
|         airport: 13 |         airport: 13, | ||||||
|  |         shop:16 | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -89,7 +94,8 @@ export class GeocodingUtils { | ||||||
|         street: "globe_alt", |         street: "globe_alt", | ||||||
|         train_station: "train", |         train_station: "train", | ||||||
|         county: "building_office_2", |         county: "building_office_2", | ||||||
|         airport: "airport" |         airport: "airport", | ||||||
|  |         shop: "building_storefront" | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -95,6 +95,9 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding | ||||||
| 
 | 
 | ||||||
|     private getCategory(entry: Feature) { |     private getCategory(entry: Feature) { | ||||||
|         const p = entry.properties |         const p = entry.properties | ||||||
|  |         if(p.osm_key === "shop"){ | ||||||
|  |             return "shop" | ||||||
|  |         } | ||||||
|         if (p.osm_value === "train_station" || p.osm_key === "railway") { |         if (p.osm_value === "train_station" || p.osm_key === "railway") { | ||||||
|             return "train_station" |             return "train_station" | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -12,10 +12,41 @@ export class RecentSearch { | ||||||
|     public readonly seenThisSession: Store<GeoCodeResult[]> |     public readonly seenThisSession: Store<GeoCodeResult[]> | ||||||
| 
 | 
 | ||||||
|     constructor(state: { layout: LayoutConfig, osmConnection: OsmConnection, selectedElement: Store<Feature> }) { |     constructor(state: { layout: LayoutConfig, osmConnection: OsmConnection, selectedElement: Store<Feature> }) { | ||||||
|      //   const prefs = state.osmConnection.preferencesHandler.GetLongPreference("previous-searches")
 |         const prefs = state.osmConnection.preferencesHandler.GetLongPreference("previous-searches") | ||||||
|  |         prefs.addCallbackAndRunD(prev => console.trace("Previous searches are:", prev)) | ||||||
|  |         prefs.set(null) | ||||||
|         this._seenThisSession =  new UIEventSource<GeoCodeResult[]>([])//UIEventSource.asObject<GeoCodeResult[]>(prefs, [])
 |         this._seenThisSession =  new UIEventSource<GeoCodeResult[]>([])//UIEventSource.asObject<GeoCodeResult[]>(prefs, [])
 | ||||||
|         this.seenThisSession = this._seenThisSession |         this.seenThisSession = this._seenThisSession | ||||||
| 
 | 
 | ||||||
|  |         prefs.addCallbackAndRunD(prefs => { | ||||||
|  |             if(prefs === ""){ | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|  |             const simpleArr = <GeoCodeResult[]> JSON.parse(prefs) | ||||||
|  |             if(simpleArr.length > 0){ | ||||||
|  |                 this._seenThisSession.set(simpleArr) | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         this.seenThisSession.stabilized(2500).addCallbackAndRunD(seen => { | ||||||
|  |             const results=  [] | ||||||
|  |             for (let i = 0; i < Math.min(3, seen.length); i++) { | ||||||
|  |                 const gc = seen[i] | ||||||
|  |                 const simple = { | ||||||
|  |                     category: gc.category, | ||||||
|  |                     description: gc.description, | ||||||
|  |                     display_name: gc.display_name, | ||||||
|  |                     lat: gc.lat, lon: gc.lon, | ||||||
|  |                     osm_id: gc.osm_id, | ||||||
|  |                     osm_type: gc.osm_type | ||||||
|  |                 } | ||||||
|  |                 results.push(simple) | ||||||
|  |             } | ||||||
|  |             console.log("Setting", results) | ||||||
|  |             prefs.setData(JSON.stringify(results)) | ||||||
|  | 
 | ||||||
|  |         }) | ||||||
| 
 | 
 | ||||||
|         state.selectedElement.addCallbackAndRunD(selected => { |         state.selectedElement.addCallbackAndRunD(selected => { | ||||||
| 
 | 
 | ||||||
|  | @ -23,6 +54,10 @@ export class RecentSearch { | ||||||
|             if(!osm_id){ |             if(!osm_id){ | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
|  |             console.log("Selected element is", selected) | ||||||
|  |             if(["node","way","relation"].indexOf(osm_type) < 0){ | ||||||
|  |                 return | ||||||
|  |             } | ||||||
|             const [lon, lat] = GeoOperations.centerpointCoordinates(selected) |             const [lon, lat] = GeoOperations.centerpointCoordinates(selected) | ||||||
|             const entry = <GeoCodeResult>{ |             const entry = <GeoCodeResult>{ | ||||||
|                 feature: selected, |                 feature: selected, | ||||||
|  | @ -46,6 +81,7 @@ export class RecentSearch { | ||||||
|                 seenIds.add(id) |                 seenIds.add(id) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         console.log(">>>",arr) | ||||||
|         this._seenThisSession.set(arr) |         this._seenThisSession.set(arr) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,9 +2,7 @@ import { UIEventSource } from "../UIEventSource" | ||||||
| import UserDetails, { OsmConnection } from "./OsmConnection" | import UserDetails, { OsmConnection } from "./OsmConnection" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||||
| // @ts-ignore
 | import OSMAuthInstance = OSMAuth.osmAuth | ||||||
| import { osmAuth } from "osm-auth" |  | ||||||
| import OSMAuthInstance = OSMAuth.OSMAuthInstance |  | ||||||
| 
 | 
 | ||||||
| export class OsmPreferences { | export class OsmPreferences { | ||||||
|     /** |     /** | ||||||
|  | @ -53,7 +51,7 @@ export class OsmPreferences { | ||||||
|         const subOptions = { prefix: "" } |         const subOptions = { prefix: "" } | ||||||
|         // Gives the number of combined preferences
 |         // Gives the number of combined preferences
 | ||||||
|         const length = this.GetPreference(allStartWith + "-length", "", subOptions) |         const length = this.GetPreference(allStartWith + "-length", "", subOptions) | ||||||
| 
 |         const preferences = this.preferences | ||||||
|         if ((allStartWith + "-length").length > 255) { |         if ((allStartWith + "-length").length > 255) { | ||||||
|             throw ( |             throw ( | ||||||
|                 "This preference key is too long, it has " + |                 "This preference key is too long, it has " + | ||||||
|  | @ -64,7 +62,6 @@ export class OsmPreferences { | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const self = this |  | ||||||
|         source.addCallback((str) => { |         source.addCallback((str) => { | ||||||
|             if (str === undefined || str === "") { |             if (str === undefined || str === "") { | ||||||
|                 return |                 return | ||||||
|  | @ -74,9 +71,9 @@ export class OsmPreferences { | ||||||
|                 const count = parseInt(length.data) |                 const count = parseInt(length.data) | ||||||
|                 for (let i = 0; i < count; i++) { |                 for (let i = 0; i < count; i++) { | ||||||
|                     // Delete all the preferences
 |                     // Delete all the preferences
 | ||||||
|                     self.GetPreference(allStartWith + "-" + i, "", subOptions).setData("") |                     this.GetPreference(allStartWith + "-" + i, "", subOptions).setData("") | ||||||
|                 } |                 } | ||||||
|                 self.GetPreference(allStartWith + "-length", "", subOptions).setData("") |                 this.GetPreference(allStartWith + "-length", "", subOptions).setData("") | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -99,7 +96,7 @@ export class OsmPreferences { | ||||||
|                 if (i > 100) { |                 if (i > 100) { | ||||||
|                     throw "This long preference is getting very long... " |                     throw "This long preference is getting very long... " | ||||||
|                 } |                 } | ||||||
|                 self.GetPreference(allStartWith + "-" + i, "", subOptions).setData( |                 this.GetPreference(allStartWith + "-" + i, "", subOptions).setData( | ||||||
|                     str.substr(0, 255) |                     str.substr(0, 255) | ||||||
|                 ) |                 ) | ||||||
|                 str = str.substr(255) |                 str = str.substr(255) | ||||||
|  | @ -108,8 +105,9 @@ export class OsmPreferences { | ||||||
|             length.setData("" + i) // We use I, the number of preference fields used
 |             length.setData("" + i) // We use I, the number of preference fields used
 | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|         function updateData(l: number) { |         function updateData(l: number) { | ||||||
|             if (Object.keys(self.preferences.data).length === 0) { |             if (Object.keys(preferences.data).length === 0) { | ||||||
|                 // The preferences are still empty - they are not yet updated, so we delay updating for now
 |                 // The preferences are still empty - they are not yet updated, so we delay updating for now
 | ||||||
|                 return |                 return | ||||||
|             } |             } | ||||||
|  | @ -120,15 +118,21 @@ export class OsmPreferences { | ||||||
|             let str = "" |             let str = "" | ||||||
|             for (let i = 0; i < prefsCount; i++) { |             for (let i = 0; i < prefsCount; i++) { | ||||||
|                 const key = allStartWith + "-" + i |                 const key = allStartWith + "-" + i | ||||||
|                 if (self.preferences.data[key] === undefined) { |                 if (preferences.data[key] === undefined) { | ||||||
|                     console.warn( |                     console.warn( | ||||||
|                         "Detected a broken combined preference:", |                         "Detected a broken combined preference:", | ||||||
|                         key, |                         key, | ||||||
|                         "is undefined", |                         "is undefined", | ||||||
|                         self.preferences |                         preferences | ||||||
|                     ) |                     ) | ||||||
|  |                     continue | ||||||
|                 } |                 } | ||||||
|                 str += self.preferences.data[key] ?? "" |                 const v = preferences.data[key] | ||||||
|  |                 if(v === "undefined"){ | ||||||
|  |                     delete preferences.data[key] | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 str += preferences.data[key] ?? "" | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             source.setData(str) |             source.setData(str) | ||||||
|  | @ -137,7 +141,7 @@ export class OsmPreferences { | ||||||
|         length.addCallback((l) => { |         length.addCallback((l) => { | ||||||
|             updateData(Number(l)) |             updateData(Number(l)) | ||||||
|         }) |         }) | ||||||
|         this.preferences.addCallbackAndRun((_) => { |         this.preferences.addCallbackAndRun(() => { | ||||||
|             updateData(Number(length.data)) |             updateData(Number(length.data)) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  | @ -159,7 +163,7 @@ export class OsmPreferences { | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|         key = prefix + key |         key = prefix + key | ||||||
|         key = key.replace(/[:\\\/"' {}.%]/g, "") |         key = key.replace(/[:/"' {}.%\\]/g, "") | ||||||
|         if (key.length >= 255) { |         if (key.length >= 255) { | ||||||
|             throw "Preferences: key length to big" |             throw "Preferences: key length to big" | ||||||
|         } |         } | ||||||
|  | @ -193,7 +197,6 @@ export class OsmPreferences { | ||||||
| 
 | 
 | ||||||
|     public ClearPreferences() { |     public ClearPreferences() { | ||||||
|         let isRunning = false |         let isRunning = false | ||||||
|         const self = this |  | ||||||
|         this.preferences.addCallback((prefs) => { |         this.preferences.addCallback((prefs) => { | ||||||
|             console.log("Cleaning preferences...") |             console.log("Cleaning preferences...") | ||||||
|             if (Object.keys(prefs).length == 0) { |             if (Object.keys(prefs).length == 0) { | ||||||
|  | @ -208,7 +211,7 @@ export class OsmPreferences { | ||||||
|                 const matches = prefixes.some((prefix) => key.startsWith(prefix)) |                 const matches = prefixes.some((prefix) => key.startsWith(prefix)) | ||||||
|                 if (matches) { |                 if (matches) { | ||||||
|                     console.log("Clearing ", key) |                     console.log("Clearing ", key) | ||||||
|                     self.GetPreference(key, "", { prefix: "" }).setData("") |                     this.GetPreference(key, "", { prefix: "" }).setData("") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             isRunning = false |             isRunning = false | ||||||
|  | @ -227,7 +230,6 @@ export class OsmPreferences { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private UpdatePreferences(forceUpdate?: boolean) { |     private UpdatePreferences(forceUpdate?: boolean) { | ||||||
|         const self = this |  | ||||||
|         if (this._fakeUser) { |         if (this._fakeUser) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|  | @ -236,7 +238,7 @@ export class OsmPreferences { | ||||||
|                 method: "GET", |                 method: "GET", | ||||||
|                 path: "/api/0.6/user/preferences", |                 path: "/api/0.6/user/preferences", | ||||||
|             }, |             }, | ||||||
|             function (error, value: XMLDocument) { |              (error, value: XMLDocument) => { | ||||||
|                 if (error) { |                 if (error) { | ||||||
|                     console.log("Could not load preferences", error) |                     console.log("Could not load preferences", error) | ||||||
|                     return |                     return | ||||||
|  | @ -246,34 +248,33 @@ export class OsmPreferences { | ||||||
|                 for (let i = 0; i < prefs.length; i++) { |                 for (let i = 0; i < prefs.length; i++) { | ||||||
|                     const pref = prefs[i] |                     const pref = prefs[i] | ||||||
|                     const k = pref.getAttribute("k") |                     const k = pref.getAttribute("k") | ||||||
|                     const v = pref.getAttribute("v") |                     this.preferences.data[k] = pref.getAttribute("v") | ||||||
|                     self.preferences.data[k] = v |  | ||||||
|                     seenKeys.add(k) |                     seenKeys.add(k) | ||||||
|                 } |                 } | ||||||
|                 if (forceUpdate) { |                 if (forceUpdate) { | ||||||
|                     for (let key in self.preferences.data) { |                     for (const key in this.preferences.data) { | ||||||
|                         if (seenKeys.has(key)) { |                         if (seenKeys.has(key)) { | ||||||
|                             continue |                             continue | ||||||
|                         } |                         } | ||||||
|                         console.log("Deleting key", key, "as we didn't find it upstream") |                         console.log("Deleting key", key, "as we didn't find it upstream") | ||||||
|                         delete self.preferences.data[key] |                         delete this.preferences.data[key] | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 // We merge all the preferences: new keys are uploaded
 |                 // We merge all the preferences: new keys are uploaded
 | ||||||
|                 // For differing values, the server overrides local changes
 |                 // For differing values, the server overrides local changes
 | ||||||
|                 self.preferenceSources.forEach((preference, key) => { |                 this.preferenceSources.forEach((preference, key) => { | ||||||
|                     const osmValue = self.preferences.data[key] |                     const osmValue = this.preferences.data[key] | ||||||
|                     if (osmValue === undefined && preference.data !== undefined) { |                     if (osmValue === undefined && preference.data !== undefined) { | ||||||
|                         // OSM doesn't know this value yet
 |                         // OSM doesn't know this value yet
 | ||||||
|                         self.UploadPreference(key, preference.data) |                         this.UploadPreference(key, preference.data) | ||||||
|                     } else { |                     } else { | ||||||
|                         // OSM does have a value - set it
 |                         // OSM does have a value - set it
 | ||||||
|                         preference.setData(osmValue) |                         preference.setData(osmValue) | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
| 
 | 
 | ||||||
|                 self.preferences.ping() |                 this.preferences.ping() | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  | @ -287,7 +288,6 @@ export class OsmPreferences { | ||||||
|         if (this.preferences.data[k] === v) { |         if (this.preferences.data[k] === v) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
|         const self = this |  | ||||||
|         console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15)) |         console.debug("Updating preference", k, " to ", Utils.EllipsesAfter(v, 15)) | ||||||
|         if (this._fakeUser) { |         if (this._fakeUser) { | ||||||
|             return |             return | ||||||
|  | @ -299,13 +299,13 @@ export class OsmPreferences { | ||||||
|                     path: "/api/0.6/user/preferences/" + encodeURIComponent(k), |                     path: "/api/0.6/user/preferences/" + encodeURIComponent(k), | ||||||
|                     headers: { "Content-Type": "text/plain" }, |                     headers: { "Content-Type": "text/plain" }, | ||||||
|                 }, |                 }, | ||||||
|                 function (error) { |                 (error) => { | ||||||
|                     if (error) { |                     if (error) { | ||||||
|                         console.warn("Could not remove preference", error) |                         console.warn("Could not remove preference", error) | ||||||
|                         return |                         return | ||||||
|                     } |                     } | ||||||
|                     delete self.preferences.data[k] |                     delete this.preferences.data[k] | ||||||
|                     self.preferences.ping() |                     this.preferences.ping() | ||||||
|                     console.debug("Preference ", k, "removed!") |                     console.debug("Preference ", k, "removed!") | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|  | @ -319,13 +319,13 @@ export class OsmPreferences { | ||||||
|                 headers: { "Content-Type": "text/plain" }, |                 headers: { "Content-Type": "text/plain" }, | ||||||
|                 content: v, |                 content: v, | ||||||
|             }, |             }, | ||||||
|             function (error) { |             (error)=>  { | ||||||
|                 if (error) { |                 if (error) { | ||||||
|                     console.warn(`Could not set preference "${k}"'`, error) |                     console.warn(`Could not set preference "${k}"'`, error) | ||||||
|                     return |                     return | ||||||
|                 } |                 } | ||||||
|                 self.preferences.data[k] = v |                 this.preferences.data[k] = v | ||||||
|                 self.preferences.ping() |                 this.preferences.ping() | ||||||
|                 console.debug(`Preference ${k} written!`) |                 console.debug(`Preference ${k} written!`) | ||||||
|             } |             } | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -351,8 +351,10 @@ export default class UserRelatedState { | ||||||
|                     const key = k.substring(0, k.length - "length".length) |                     const key = k.substring(0, k.length - "length".length) | ||||||
|                     let combined = "" |                     let combined = "" | ||||||
|                     for (let i = 0; i < l; i++) { |                     for (let i = 0; i < l; i++) { | ||||||
|                         combined += newPrefs[key + i] |                         console.log("Building preference:",key,i,">>>", newPrefs[key + i], "<<<", newPrefs, ) | ||||||
|  |                         combined += (newPrefs[key + i]) | ||||||
|                     } |                     } | ||||||
|  |                     console.log("Combined",key,">>>",combined) | ||||||
|                     amendedPrefs.data[key.substring(0, key.length - "-combined-".length)] = combined |                     amendedPrefs.data[key.substring(0, key.length - "-combined-".length)] = combined | ||||||
|                 } else { |                 } else { | ||||||
|                     amendedPrefs.data[k] = newPrefs[k] |                     amendedPrefs.data[k] = newPrefs[k] | ||||||
|  | @ -456,11 +458,15 @@ export default class UserRelatedState { | ||||||
|         amendedPrefs.addCallbackD((tags) => { |         amendedPrefs.addCallbackD((tags) => { | ||||||
|             for (const key in tags) { |             for (const key in tags) { | ||||||
|                 if (key.startsWith("_") || key === "mapcomplete-language") { |                 if (key.startsWith("_") || key === "mapcomplete-language") { | ||||||
|                     // Language is managed seperately
 |                     // Language is managed separately
 | ||||||
|                     continue |                     continue | ||||||
|                 } |                 } | ||||||
|                 if (tags[key + "-combined-0"]) { |                 if (tags[key + "-combined-0"]) { | ||||||
|                     // A combined value exists
 |                     // A combined value exists
 | ||||||
|  |                     if(tags[key].startsWith("undefined")){ | ||||||
|  |                         // Sometimes, a long string of 'undefined' will show up, we ignore them
 | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|                     this.osmConnection.GetLongPreference(key, "").setData(tags[key]) |                     this.osmConnection.GetLongPreference(key, "").setData(tags[key]) | ||||||
|                 } else { |                 } else { | ||||||
|                     this.osmConnection |                     this.osmConnection | ||||||
|  |  | ||||||
|  | @ -91,6 +91,20 @@ export class Stores { | ||||||
|         }) |         }) | ||||||
|         return stable |         return stable | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * | ||||||
|  |      * Constructs a new store, but tries to keep the value 'defined' | ||||||
|  |      * If a defined value was in the stream once, a defined value will be returned | ||||||
|  |      * @param store | ||||||
|  |      */ | ||||||
|  |     static holdDefined<T>(store: Store<T | undefined>): Store<T | undefined> { | ||||||
|  |         const newStore = new UIEventSource(store.data) | ||||||
|  |         store.addCallbackD(t => { | ||||||
|  |             newStore.setData(t) | ||||||
|  |         }) | ||||||
|  |         return newStore | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export abstract class Store<T> implements Readable<T> { | export abstract class Store<T> implements Readable<T> { | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ export default class Constants { | ||||||
|         "last_click", |         "last_click", | ||||||
|         "favourite", |         "favourite", | ||||||
|         "summary", |         "summary", | ||||||
|  |         "search" | ||||||
|     ] as const |     ] as const | ||||||
|     /** |     /** | ||||||
|      * Special layers which are not included in a theme by default |      * Special layers which are not included in a theme by default | ||||||
|  | @ -38,7 +39,7 @@ export default class Constants { | ||||||
|         "import_candidate", |         "import_candidate", | ||||||
|         "usersettings", |         "usersettings", | ||||||
|         "icons", |         "icons", | ||||||
|         "filters", |         "filters" | ||||||
|     ] as const |     ] as const | ||||||
|     /** |     /** | ||||||
|      * Layer IDs of layers which have special properties through built-in hooks |      * Layer IDs of layers which have special properties through built-in hooks | ||||||
|  | @ -126,6 +127,7 @@ export default class Constants { | ||||||
|         "brick_wall_round", |         "brick_wall_round", | ||||||
|         "brick_wall_square", |         "brick_wall_square", | ||||||
|         "building_office_2", |         "building_office_2", | ||||||
|  |         "building_storefront", | ||||||
|         "bug", |         "bug", | ||||||
|         "checkmark", |         "checkmark", | ||||||
|         "checkmark", |         "checkmark", | ||||||
|  |  | ||||||
|  | @ -340,7 +340,7 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         console.log("Fallthrough", this, tags) |         console.trace("Fallthrough: could not find the appropraite layer for an object with tags", tags, "within layout", this) | ||||||
|         return undefined |         return undefined | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -354,7 +354,7 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|             ...json, |             ...json, | ||||||
|             layers: json.layers.filter((l) => l["id"] !== "favourite"), |             layers: json.layers.filter((l) => l["id"] !== "favourite"), | ||||||
|         } |         } | ||||||
|         const usedImages = json._usedImages |         const usedImages = jsonNoFavourites._usedImages | ||||||
|         usedImages.sort() |         usedImages.sort() | ||||||
| 
 | 
 | ||||||
|         this.usedImages = Utils.Dedup(usedImages) |         this.usedImages = Utils.Dedup(usedImages) | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ import Locale from "../UI/i18n/Locale" | ||||||
| import Hash from "../Logic/Web/Hash" | import Hash from "../Logic/Web/Hash" | ||||||
| import { GeoOperations } from "../Logic/GeoOperations" | import { GeoOperations } from "../Logic/GeoOperations" | ||||||
| import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" | import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" | ||||||
| import GeocodingProvider from "../Logic/Geocoding/GeocodingProvider" | import GeocodingProvider, { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider" | ||||||
| import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher" | import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher" | ||||||
| import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch" | import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch" | ||||||
| import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch" | import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch" | ||||||
|  | @ -774,6 +774,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|             favourite: this.favourites, |             favourite: this.favourites, | ||||||
|             summary: this.featureSummary, |             summary: this.featureSummary, | ||||||
|             last_click: this.lastClickObject, |             last_click: this.lastClickObject, | ||||||
|  |             search: undefined | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.closestFeatures.registerSource(specialLayers.favourite, "favourite") |         this.closestFeatures.registerSource(specialLayers.favourite, "favourite") | ||||||
|  | @ -910,6 +911,34 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|         this.selectedElement.setData(this.currentView.features?.data?.[0]) |         this.selectedElement.setData(this.currentView.features?.data?.[0]) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Searches the appropriate layer - will first try if a special layer matches; if not, a normal layer will be used by delegating to the layout | ||||||
|  |      * @param tags | ||||||
|  |      */ | ||||||
|  |     public getMatchingLayer(properties: Record<string, string>){ | ||||||
|  | 
 | ||||||
|  |         const id = properties.id | ||||||
|  | 
 | ||||||
|  |         if (id.startsWith("summary_")) { | ||||||
|  |             // We don't select 'summary'-objects
 | ||||||
|  |             return undefined | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (id === "settings") { | ||||||
|  |             return UserRelatedState.usersettingsConfig | ||||||
|  |         } | ||||||
|  |         if (id.startsWith(LastClickFeatureSource.newPointElementId)) { | ||||||
|  |             return this.layout.layers.find((l) => l.id === "last_click") | ||||||
|  |         } | ||||||
|  |         if (id.startsWith("search_result")) { | ||||||
|  |             return GeocodingUtils.searchLayer | ||||||
|  |         } | ||||||
|  |         if (id === "location_track") { | ||||||
|  |             return this.layout.layers.find((l) => l.id === "gps_track") | ||||||
|  |         } | ||||||
|  |         return this.layout.getMatchingLayer(properties) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public async reportError(message: string | Error | XMLHttpRequest) { |     public async reportError(message: string | Error | XMLHttpRequest) { | ||||||
|         const isTesting = this.featureSwitchIsTesting.data |         const isTesting = this.featureSwitchIsTesting.data | ||||||
|         console.log( |         console.log( | ||||||
|  |  | ||||||
|  | @ -7,23 +7,17 @@ | ||||||
|   import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource" |   import { LastClickFeatureSource } from "../../Logic/FeatureSource/Sources/LastClickFeatureSource" | ||||||
|   import Loading from "./Loading.svelte" |   import Loading from "./Loading.svelte" | ||||||
|   import { onDestroy } from "svelte" |   import { onDestroy } from "svelte" | ||||||
|  |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|  |   import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider" | ||||||
|  |   import ThemeViewState from "../../Models/ThemeViewState" | ||||||
| 
 | 
 | ||||||
|   export let state: SpecialVisualizationState |   export let state: SpecialVisualizationState | ||||||
|   export let selected: Feature |   export let selected: Feature | ||||||
|   let tags = state.featureProperties.getStore(selected.properties.id) |   let tags = state.featureProperties.getStore(selected.properties.id) | ||||||
| 
 | 
 | ||||||
|   export let absolute = true |   export let absolute = true | ||||||
|   function getLayer(properties: Record<string, string>) { |   function getLayer(properties: Record<string, string>): LayerConfig { | ||||||
|     if (properties.id === "settings") { |     return state.getMatchingLayer(properties) | ||||||
|       return UserRelatedState.usersettingsConfig |  | ||||||
|     } |  | ||||||
|     if (properties.id.startsWith(LastClickFeatureSource.newPointElementId)) { |  | ||||||
|       return state.layout.layers.find((l) => l.id === "last_click") |  | ||||||
|     } |  | ||||||
|     if (properties.id === "location_track") { |  | ||||||
|       return state.layout.layers.find((l) => l.id === "gps_track") |  | ||||||
|     } |  | ||||||
|     return state.layout.getMatchingLayer(properties) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   let layer = getLayer(selected.properties) |   let layer = getLayer(selected.properties) | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource" |   import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|   import type { Feature } from "geojson" |   import type { Feature } from "geojson" | ||||||
|   import Translations from "../i18n/Translations" |   import Translations from "../i18n/Translations" | ||||||
|   import Loading from "../Base/Loading.svelte" |   import Loading from "../Base/Loading.svelte" | ||||||
|  | @ -17,10 +17,14 @@ | ||||||
|   import type GeocodingProvider from "../../Logic/Geocoding/GeocodingProvider" |   import type GeocodingProvider from "../../Logic/Geocoding/GeocodingProvider" | ||||||
| 
 | 
 | ||||||
|   import SearchResults from "./SearchResults.svelte" |   import SearchResults from "./SearchResults.svelte" | ||||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" |  | ||||||
|   import MoreScreen from "./MoreScreen" |   import MoreScreen from "./MoreScreen" | ||||||
|   import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" |   import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" | ||||||
|   import { focusWithArrows } from "../../Utils/focusWithArrows" |   import { focusWithArrows } from "../../Utils/focusWithArrows" | ||||||
|  |   import ShowDataLayer from "../Map/ShowDataLayer" | ||||||
|  |   import ThemeViewState from "../../Models/ThemeViewState" | ||||||
|  |   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||||
|  |   import GeocodingFeatureSource from "../../Logic/Geocoding/GeocodingFeatureSource" | ||||||
|  |   import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson.js" | ||||||
| 
 | 
 | ||||||
|   export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined |   export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined | ||||||
|   export let bounds: UIEventSource<BBox> |   export let bounds: UIEventSource<BBox> | ||||||
|  | @ -29,11 +33,11 @@ | ||||||
|   export let geolocationState: GeoLocationState | undefined = undefined |   export let geolocationState: GeoLocationState | undefined = undefined | ||||||
|   export let clearAfterView: boolean = true |   export let clearAfterView: boolean = true | ||||||
|   export let searcher: GeocodingProvider = new NominatimGeocoding() |   export let searcher: GeocodingProvider = new NominatimGeocoding() | ||||||
|   export let state: SpecialVisualizationState |   export let state: ThemeViewState | ||||||
|   let searchContents: UIEventSource<string> = new UIEventSource<string>("") |   let searchContents: UIEventSource<string> = new UIEventSource<string>("") | ||||||
|   export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined) |   export let triggerSearch: UIEventSource<any> = new UIEventSource<any>(undefined) | ||||||
|   onDestroy( |   onDestroy( | ||||||
|     triggerSearch.addCallback((_) => { |     triggerSearch.addCallback(() => { | ||||||
|       performSearch() |       performSearch() | ||||||
|     }) |     }) | ||||||
|   ) |   ) | ||||||
|  | @ -139,7 +143,18 @@ | ||||||
|       if (search.length === 0) { |       if (search.length === 0) { | ||||||
|         return undefined |         return undefined | ||||||
|       } |       } | ||||||
|       return searcher.suggest(search, { bbox: bounds.data }) |       return Stores.holdDefined(bounds.bindD(bbox => searcher.suggest(search, { bbox, limit: 15 }))) | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  |   let geocededFeatures=  new GeocodingFeatureSource(suggestions.stabilized(250)) | ||||||
|  |   state.featureProperties.trackFeatureSource(geocededFeatures) | ||||||
|  | 
 | ||||||
|  |   new ShowDataLayer( | ||||||
|  |     state.map, | ||||||
|  |     { | ||||||
|  |       layer: GeocodingUtils.searchLayer, | ||||||
|  |       features:  geocededFeatures, | ||||||
|  |       selectedElement: state.selectedElement | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| 
 | 
 | ||||||
|  | @ -147,19 +162,18 @@ | ||||||
| 
 | 
 | ||||||
|   function checkFocus() { |   function checkFocus() { | ||||||
|     window.requestAnimationFrame(() => { |     window.requestAnimationFrame(() => { | ||||||
|       if (geosearch.contains(document.activeElement)) { |       if (geosearch?.contains(document.activeElement)) { | ||||||
|         return |         return | ||||||
|       } |       } | ||||||
|       isFocused.setData(false) |       isFocused.setData(false) | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   document.addEventListener("focus",() => { |   document.addEventListener("focus", () => { | ||||||
|     checkFocus() |     checkFocus() | ||||||
|   }, true /* use 'capturing' instead of bubbling, needed for focus-events*/) |   }, true /* use 'capturing' instead of bubbling, needed for focus-events*/) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div bind:this={geosearch} use:focusWithArrows={"searchresult"}> | <div bind:this={geosearch} use:focusWithArrows={"searchresult"}> | ||||||
|  |  | ||||||
|  | @ -42,6 +42,7 @@ | ||||||
|   import BuildingOffice2 from "@babeard/svelte-heroicons/outline/BuildingOffice2" |   import BuildingOffice2 from "@babeard/svelte-heroicons/outline/BuildingOffice2" | ||||||
|   import Train from "../../assets/svg/Train.svelte" |   import Train from "../../assets/svg/Train.svelte" | ||||||
|   import Airport from "../../assets/svg/Airport.svelte" |   import Airport from "../../assets/svg/Airport.svelte" | ||||||
|  |   import BuildingStorefront from "@babeard/svelte-heroicons/outline/BuildingStorefront" | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Renders a single icon. |    * Renders a single icon. | ||||||
|  | @ -159,6 +160,8 @@ | ||||||
|     <Train {color} class={clss}/> |     <Train {color} class={clss}/> | ||||||
|   {:else if icon === "airport"} |   {:else if icon === "airport"} | ||||||
|     <Airport {color} class={clss}/> |     <Airport {color} class={clss}/> | ||||||
|  |   {:else if icon === "building_storefront"} | ||||||
|  |     <BuildingStorefront {color} class={clss}/> | ||||||
|   {:else if Utils.isEmoji(icon)} |   {:else if Utils.isEmoji(icon)} | ||||||
|     <span style={`font-size: ${emojiHeight}px; line-height: ${emojiHeight}px`}> |     <span style={`font-size: ${emojiHeight}px; line-height: ${emojiHeight}px`}> | ||||||
|       {icon} |       {icon} | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import LayoutConfig, { MinimalLayoutInformation } from "../Models/ThemeConfig/La | ||||||
| import { | import { | ||||||
|     FeatureSource, |     FeatureSource, | ||||||
|     IndexedFeatureSource, |     IndexedFeatureSource, | ||||||
|     WritableFeatureSource, |     WritableFeatureSource | ||||||
| } from "../Logic/FeatureSource/FeatureSource" | } from "../Logic/FeatureSource/FeatureSource" | ||||||
| import { OsmConnection } from "../Logic/Osm/OsmConnection" | import { OsmConnection } from "../Logic/Osm/OsmConnection" | ||||||
| import { Changes } from "../Logic/Osm/Changes" | import { Changes } from "../Logic/Osm/Changes" | ||||||
|  | @ -97,8 +97,10 @@ export interface SpecialVisualizationState { | ||||||
|     readonly geolocation: GeoLocationHandler |     readonly geolocation: GeoLocationHandler | ||||||
|     readonly recentlySearched: RecentSearch |     readonly recentlySearched: RecentSearch | ||||||
| 
 | 
 | ||||||
|  |     getMatchingLayer(properties: Record<string, string>); | ||||||
| 
 | 
 | ||||||
|     showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer |     showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer | ||||||
|  | 
 | ||||||
|     reportError(message: string): Promise<void> |     reportError(message: string): Promise<void> | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -134,7 +136,7 @@ export interface SpecialVisualization { | ||||||
| export type RenderingSpecification = | export type RenderingSpecification = | ||||||
|     | string |     | string | ||||||
|     | { |     | { | ||||||
|           func: SpecialVisualization |     func: SpecialVisualization | ||||||
|           args: string[] |     args: string[] | ||||||
|           style: string |     style: string | ||||||
|       } | } | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ | ||||||
|     EyeIcon, |     EyeIcon, | ||||||
|     HeartIcon, |     HeartIcon, | ||||||
|     MenuIcon, |     MenuIcon, | ||||||
|     XCircleIcon, |     XCircleIcon | ||||||
|   } from "@rgossiaux/svelte-heroicons/solid" |   } from "@rgossiaux/svelte-heroicons/solid" | ||||||
|   import Tr from "./Base/Tr.svelte" |   import Tr from "./Base/Tr.svelte" | ||||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" |   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" | ||||||
|  | @ -72,6 +72,7 @@ | ||||||
|   import HotkeyTable from "./BigComponents/HotkeyTable.svelte" |   import HotkeyTable from "./BigComponents/HotkeyTable.svelte" | ||||||
|   import SelectedElementPanel from "./Base/SelectedElementPanel.svelte" |   import SelectedElementPanel from "./Base/SelectedElementPanel.svelte" | ||||||
|   import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" |   import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson" | ||||||
|  |   import { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider" | ||||||
| 
 | 
 | ||||||
|   export let state: ThemeViewState |   export let state: ThemeViewState | ||||||
|   let layout = state.layout |   let layout = state.layout | ||||||
|  | @ -98,22 +99,10 @@ | ||||||
|   }) |   }) | ||||||
| 
 | 
 | ||||||
|   let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => { |   let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => { | ||||||
|     const id = element.properties.id |     if (element.properties.id.startsWith("current_view")) { | ||||||
|     if (id.startsWith("current_view")) { |  | ||||||
|       return currentViewLayer |       return currentViewLayer | ||||||
|     } |     } | ||||||
|     if (id.startsWith("summary_")) { |     return state.getMatchingLayer(element.properties) | ||||||
|       console.log("Not selecting a summary object. The summary object is", element) |  | ||||||
|       return undefined |  | ||||||
|     } |  | ||||||
|     if (id.startsWith(LastClickFeatureSource.newPointElementId)) { |  | ||||||
|       return layout.layers.find((l) => l.id === "last_click") |  | ||||||
|     } |  | ||||||
|     if (id === "location_track") { |  | ||||||
|       return layout.layers.find((l) => l.id === "gps_track") |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return state.layout.getMatchingLayer(element.properties) |  | ||||||
|   }) |   }) | ||||||
|   let currentZoom = state.mapProperties.zoom |   let currentZoom = state.mapProperties.zoom | ||||||
|   let showCrosshair = state.userRelatedState.showCrosshair |   let showCrosshair = state.userRelatedState.showCrosshair | ||||||
|  | @ -144,7 +133,7 @@ | ||||||
|     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) |     const bottomRight = mlmap.unproject([rect.right, rect.bottom]) | ||||||
|     const bbox = new BBox([ |     const bbox = new BBox([ | ||||||
|       [topLeft.lng, topLeft.lat], |       [topLeft.lng, topLeft.lat], | ||||||
|       [bottomRight.lng, bottomRight.lat], |       [bottomRight.lng, bottomRight.lat] | ||||||
|     ]) |     ]) | ||||||
|     state.visualFeedbackViewportBounds.setData(bbox) |     state.visualFeedbackViewportBounds.setData(bbox) | ||||||
|   } |   } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue