forked from MapComplete/MapComplete
		
	Usertests: add 'centermessage' again which indicates if data is loading or is present, see #1457
This commit is contained in:
		
							parent
							
								
									a55b84dba1
								
							
						
					
					
						commit
						1e56eb5503
					
				
					 8 changed files with 544 additions and 375 deletions
				
			
		
							
								
								
									
										90
									
								
								Logic/Actors/NoElementsInViewDetector.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								Logic/Actors/NoElementsInViewDetector.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | ||||||
|  | import { BBox } from "../BBox" | ||||||
|  | import { Store } from "../UIEventSource" | ||||||
|  | import ThemeViewState from "../../Models/ThemeViewState" | ||||||
|  | import Constants from "../../Models/Constants" | ||||||
|  | 
 | ||||||
|  | export type FeatureViewState = | ||||||
|  |     | "no-data" | ||||||
|  |     | "zoom-to-low" | ||||||
|  |     | "has-visible-feature" | ||||||
|  |     | "all-filtered-away" | ||||||
|  | export default class NoElementsInViewDetector { | ||||||
|  |     public readonly hasFeatureInView: Store<FeatureViewState> | ||||||
|  | 
 | ||||||
|  |     constructor(themeViewState: ThemeViewState) { | ||||||
|  |         const state = themeViewState | ||||||
|  |         const minZoom = Math.min( | ||||||
|  |             ...themeViewState.layout.layers | ||||||
|  |                 .filter((l) => Constants.priviliged_layers.indexOf(<any>l.id) < 0) | ||||||
|  |                 .map((l) => l.minzoom) | ||||||
|  |         ) | ||||||
|  |         const mapProperties = themeViewState.mapProperties | ||||||
|  | 
 | ||||||
|  |         const priviliged: Set<string> = new Set(Constants.priviliged_layers) | ||||||
|  | 
 | ||||||
|  |         this.hasFeatureInView = state.mapProperties.bounds.stabilized(100).map( | ||||||
|  |             (bbox) => { | ||||||
|  |                 if (mapProperties.zoom.data < minZoom) { | ||||||
|  |                     // Not a single layer will display anything as the zoom is to low
 | ||||||
|  |                     return "zoom-to-low" | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 for (const [layerName, source] of themeViewState.perLayerFiltered) { | ||||||
|  |                     if (priviliged.has(layerName)) { | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     if ( | ||||||
|  |                         mapProperties.zoom.data < themeViewState.layout.getLayer(layerName).minzoom | ||||||
|  |                     ) { | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     if (!state.layerState.filteredLayers.get(layerName).isDisplayed.data) { | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     const feats = source.features.data | ||||||
|  |                     if (!(feats?.length > 0)) { | ||||||
|  |                         // Nope, no data loaded
 | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     for (const feat of feats) { | ||||||
|  |                         if (BBox.get(feat).overlapsWith(bbox)) { | ||||||
|  |                             // We found at least one item which has visible data
 | ||||||
|  |                             return "has-visible-feature" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // If we arrive here, data might have been filtered away
 | ||||||
|  |                 for (const [layerName, source] of themeViewState.perLayer) { | ||||||
|  |                     if (priviliged.has(layerName)) { | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     if ( | ||||||
|  |                         mapProperties.zoom.data < themeViewState.layout.getLayer(layerName).minzoom | ||||||
|  |                     ) { | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     const feats = source.features.data | ||||||
|  |                     if (!(feats?.length > 0)) { | ||||||
|  |                         // Nope, no data loaded
 | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     for (const feat of feats) { | ||||||
|  |                         if (BBox.get(feat).overlapsWith(bbox)) { | ||||||
|  |                             // We found at least one item, but as we didn't find it before, it is filtered away
 | ||||||
|  |                             return "all-filtered-away" | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 return "no-data" | ||||||
|  |             }, | ||||||
|  |             [ | ||||||
|  |                 ...Array.from(themeViewState.perLayerFiltered.values()).map((f) => f.features), | ||||||
|  |                 mapProperties.zoom, | ||||||
|  |                 ...Array.from(state.layerState.filteredLayers.values()).map((fl) => fl.isDisplayed), | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -69,7 +69,7 @@ export default class LayoutSource extends FeatureSourceMerger { | ||||||
| 
 | 
 | ||||||
|         const self = this |         const self = this | ||||||
|         function setIsLoading() { |         function setIsLoading() { | ||||||
|             const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data |             const loading = overpassSource?.runningQuery?.data || osmApiSource?.isRunning?.data | ||||||
|             self._isLoading.setData(loading) |             self._isLoading.setData(loading) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -384,7 +384,6 @@ class ListenerTracker<T> { | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * The mapped store is a helper type which does the mapping of a function. |  * The mapped store is a helper type which does the mapping of a function. | ||||||
|  * It'll fuse |  | ||||||
|  */ |  */ | ||||||
| class MappedStore<TIn, T> extends Store<T> { | class MappedStore<TIn, T> extends Store<T> { | ||||||
|     private readonly _upstream: Store<TIn> |     private readonly _upstream: Store<TIn> | ||||||
|  |  | ||||||
|  | @ -70,6 +70,8 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|     public readonly definedAtUrl?: string |     public readonly definedAtUrl?: string | ||||||
|     public readonly definitionRaw?: string |     public readonly definitionRaw?: string | ||||||
| 
 | 
 | ||||||
|  |     private readonly layersDict: Map<string, LayerConfig> | ||||||
|  | 
 | ||||||
|     constructor( |     constructor( | ||||||
|         json: LayoutConfigJson, |         json: LayoutConfigJson, | ||||||
|         official = true, |         official = true, | ||||||
|  | @ -209,6 +211,11 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|         this.overpassTimeout = json.overpassTimeout ?? 30 |         this.overpassTimeout = json.overpassTimeout ?? 30 | ||||||
|         this.overpassMaxZoom = json.overpassMaxZoom ?? 16 |         this.overpassMaxZoom = json.overpassMaxZoom ?? 16 | ||||||
|         this.osmApiTileSize = json.osmApiTileSize ?? this.overpassMaxZoom + 1 |         this.osmApiTileSize = json.osmApiTileSize ?? this.overpassMaxZoom + 1 | ||||||
|  | 
 | ||||||
|  |         this.layersDict = new Map<string, LayerConfig>() | ||||||
|  |         for (const layer of this.layers) { | ||||||
|  |             this.layersDict.set(layer.id, layer) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public CustomCodeSnippets(): string[] { |     public CustomCodeSnippets(): string[] { | ||||||
|  | @ -228,6 +235,10 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|         return custom |         return custom | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public getLayer(id: string) { | ||||||
|  |         return this.layersDict.get(id) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public isLeftRightSensitive() { |     public isLeftRightSensitive() { | ||||||
|         return this.layers.some((l) => l.isLeftRightSensitive()) |         return this.layers.some((l) => l.isLeftRightSensitive()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -50,6 +50,9 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" | ||||||
| import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" | import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" | ||||||
| import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" | ||||||
| import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" | import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" | ||||||
|  | import NoElementsInViewDetector, { | ||||||
|  |     FeatureViewState, | ||||||
|  | } from "../Logic/Actors/NoElementsInViewDetector" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * |  * | ||||||
|  | @ -75,8 +78,13 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|     readonly osmObjectDownloader: OsmObjectDownloader |     readonly osmObjectDownloader: OsmObjectDownloader | ||||||
| 
 | 
 | ||||||
|     readonly dataIsLoading: Store<boolean> |     readonly dataIsLoading: Store<boolean> | ||||||
|  |     /** | ||||||
|  |      * Indicates if there is _some_ data in view, even if it is not shown due to the filters | ||||||
|  |      */ | ||||||
|  |     readonly hasDataInView: Store<FeatureViewState> | ||||||
|  | 
 | ||||||
|     readonly guistate: MenuState |     readonly guistate: MenuState | ||||||
|     readonly fullNodeDatabase?: FullNodeDatabaseSource // TODO
 |     readonly fullNodeDatabase?: FullNodeDatabaseSource | ||||||
| 
 | 
 | ||||||
|     readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> |     readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> | ||||||
|     readonly indexedFeatures: IndexedFeatureSource & LayoutSource |     readonly indexedFeatures: IndexedFeatureSource & LayoutSource | ||||||
|  | @ -85,6 +93,8 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|     readonly newFeatures: WritableFeatureSource |     readonly newFeatures: WritableFeatureSource | ||||||
|     readonly layerState: LayerState |     readonly layerState: LayerState | ||||||
|     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> |     readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | ||||||
|  |     readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource> | ||||||
|  | 
 | ||||||
|     readonly availableLayers: Store<RasterLayerPolygon[]> |     readonly availableLayers: Store<RasterLayerPolygon[]> | ||||||
|     readonly selectedLayer: UIEventSource<LayerConfig> |     readonly selectedLayer: UIEventSource<LayerConfig> | ||||||
|     readonly userRelatedState: UserRelatedState |     readonly userRelatedState: UserRelatedState | ||||||
|  | @ -175,6 +185,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|                 this.fullNodeDatabase |                 this.fullNodeDatabase | ||||||
|             ) |             ) | ||||||
|             this.indexedFeatures = layoutSource |             this.indexedFeatures = layoutSource | ||||||
|  | 
 | ||||||
|             const empty = [] |             const empty = [] | ||||||
|             let currentViewIndex = 0 |             let currentViewIndex = 0 | ||||||
|             this.currentView = new StaticFeatureSource( |             this.currentView = new StaticFeatureSource( | ||||||
|  | @ -194,6 +205,9 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|             ) |             ) | ||||||
|             this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) |             this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) | ||||||
|             this.dataIsLoading = layoutSource.isLoading |             this.dataIsLoading = layoutSource.isLoading | ||||||
|  |             this.dataIsLoading.addCallbackAndRunD((loading) => | ||||||
|  |                 console.log("Data is loading?", loading) | ||||||
|  |             ) | ||||||
| 
 | 
 | ||||||
|             const indexedElements = this.indexedFeatures |             const indexedElements = this.indexedFeatures | ||||||
|             this.featureProperties = new FeaturePropertiesStore(indexedElements) |             this.featureProperties = new FeaturePropertiesStore(indexedElements) | ||||||
|  | @ -288,7 +302,10 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|             this.changes |             this.changes | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         this.showNormalDataOn(this.map) |         this.perLayerFiltered = this.showNormalDataOn(this.map) | ||||||
|  | 
 | ||||||
|  |         this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView | ||||||
|  | 
 | ||||||
|         this.initActors() |         this.initActors() | ||||||
|         this.addLastClick(lastClick) |         this.addLastClick(lastClick) | ||||||
|         this.drawSpecialLayers() |         this.drawSpecialLayers() | ||||||
|  | @ -299,8 +316,9 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public showNormalDataOn(map: Store<MlMap>) { |     public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> { | ||||||
|         this.perLayer.forEach((fs) => { |         const filteringFeatureSource = new Map<string, FilteringFeatureSource>() | ||||||
|  |         this.perLayer.forEach((fs, layerName) => { | ||||||
|             const doShowLayer = this.mapProperties.zoom.map( |             const doShowLayer = this.mapProperties.zoom.map( | ||||||
|                 (z) => |                 (z) => | ||||||
|                     (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), |                     (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), | ||||||
|  | @ -323,6 +341,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|                 (id) => this.featureProperties.getStore(id), |                 (id) => this.featureProperties.getStore(id), | ||||||
|                 this.layerState.globalFilters |                 this.layerState.globalFilters | ||||||
|             ) |             ) | ||||||
|  |             filteringFeatureSource.set(layerName, filtered) | ||||||
| 
 | 
 | ||||||
|             new ShowDataLayer(map, { |             new ShowDataLayer(map, { | ||||||
|                 layer: fs.layer.layerDef, |                 layer: fs.layer.layerDef, | ||||||
|  | @ -333,6 +352,7 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|                 fetchStore: (id) => this.featureProperties.getStore(id), |                 fetchStore: (id) => this.featureProperties.getStore(id), | ||||||
|             }) |             }) | ||||||
|         }) |         }) | ||||||
|  |         return filteringFeatureSource | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -533,28 +553,25 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|      * Setup various services for which no reference are needed |      * Setup various services for which no reference are needed | ||||||
|      */ |      */ | ||||||
|     private initActors() { |     private initActors() { | ||||||
|         { |         // Unselect the selected element if it is panned out of view
 | ||||||
|             // Unselect the selected element if it is panned out of view
 |         this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { | ||||||
|             this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { |             const selected = this.selectedElement.data | ||||||
|                 const selected = this.selectedElement.data |             if (selected === undefined) { | ||||||
|                 if (selected === undefined) { |                 return | ||||||
|                     return |             } | ||||||
|                 } |             const bbox = BBox.get(selected) | ||||||
|                 const bbox = BBox.get(selected) |             if (!bbox.overlapsWith(bounds)) { | ||||||
|                 if (!bbox.overlapsWith(bounds)) { |                 this.selectedElement.setData(undefined) | ||||||
|                     this.selectedElement.setData(undefined) |             } | ||||||
|                 } |         }) | ||||||
|             }) | 
 | ||||||
|         } |         this.selectedElement.addCallback((selected) => { | ||||||
|         { |             if (selected === undefined) { | ||||||
|             this.selectedElement.addCallback((selected) => { |                 // We did _unselect_ an item - we always remove the lastclick-object
 | ||||||
|                 if (selected === undefined) { |                 this.lastClickObject.features.setData([]) | ||||||
|                     // We did _unselect_ an item - we always remove the lastclick-object
 |                 this.selectedLayer.setData(undefined) | ||||||
|                     this.lastClickObject.features.setData([]) |             } | ||||||
|                     this.selectedLayer.setData(undefined) |         }) | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         } |  | ||||||
|         new ThemeViewStateHashActor(this) |         new ThemeViewStateHashActor(this) | ||||||
|         new MetaTagging(this) |         new MetaTagging(this) | ||||||
|         new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) |         new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) | ||||||
|  |  | ||||||
							
								
								
									
										40
									
								
								UI/BigComponents/StateIndicator.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								UI/BigComponents/StateIndicator.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,40 @@ | ||||||
|  | <script lang="ts"> | ||||||
|  |     import ThemeViewState from "../../Models/ThemeViewState"; | ||||||
|  |     import Translations from "../i18n/Translations"; | ||||||
|  |     import Tr from "../Base/Tr.svelte"; | ||||||
|  |     import Loading from "../Base/Loading.svelte"; | ||||||
|  | 
 | ||||||
|  |     export let state: ThemeViewState | ||||||
|  |     /** | ||||||
|  |      * Gives the contributor some feedback based on the current state: | ||||||
|  |      * - is data loading? | ||||||
|  |      * - Is all data hidden due to filters? | ||||||
|  |      * - Is no data in view? | ||||||
|  |      */ | ||||||
|  | 
 | ||||||
|  |     let dataIsLoading = state.dataIsLoading | ||||||
|  |     let currentState = state.hasDataInView | ||||||
|  |     currentState.data === "" | ||||||
|  |     const t = Translations.t.centerMessage | ||||||
|  | </script> | ||||||
|  | {#if $currentState === "has-visible-features"} | ||||||
|  |     <!-- don't show anything --> | ||||||
|  | {:else if $currentState === "zoom-to-low"} | ||||||
|  |     <div class="w-fit p-4 alert"> | ||||||
|  |         <Tr t={t.zoomIn}/> | ||||||
|  |     </div> | ||||||
|  | {:else if $currentState === "all-filtered-away"} | ||||||
|  |     <div class="w-fit p-4 alert"> | ||||||
|  |         <Tr t={t.allFilteredAway}/> | ||||||
|  |     </div> | ||||||
|  | {:else if $dataIsLoading} | ||||||
|  |     <div class="w-fit p-4 alert"> | ||||||
|  |         <Loading> | ||||||
|  |             <Tr t={Translations.t.centerMessage.loadingData}/> | ||||||
|  |         </Loading> | ||||||
|  |     </div> | ||||||
|  | {:else if $currentState === "no-data"} | ||||||
|  |     <div class="w-fit p-4 alert"> | ||||||
|  |         <Tr t={t.noData}/> | ||||||
|  |     </div> | ||||||
|  | {/if} | ||||||
|  | @ -1,430 +1,440 @@ | ||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
|   import {Store, UIEventSource} from "../Logic/UIEventSource" |     import {Store, UIEventSource} from "../Logic/UIEventSource" | ||||||
|   import {Map as MlMap} from "maplibre-gl" |     import {Map as MlMap} from "maplibre-gl" | ||||||
|   import MaplibreMap from "./Map/MaplibreMap.svelte" |     import MaplibreMap from "./Map/MaplibreMap.svelte" | ||||||
|   import FeatureSwitchState from "../Logic/State/FeatureSwitchState" |     import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||||
|   import MapControlButton from "./Base/MapControlButton.svelte" |     import MapControlButton from "./Base/MapControlButton.svelte" | ||||||
|   import ToSvelte from "./Base/ToSvelte.svelte" |     import ToSvelte from "./Base/ToSvelte.svelte" | ||||||
|   import If from "./Base/If.svelte" |     import If from "./Base/If.svelte" | ||||||
|   import {GeolocationControl} from "./BigComponents/GeolocationControl" |     import {GeolocationControl} from "./BigComponents/GeolocationControl" | ||||||
|   import type {Feature} from "geojson" |     import type {Feature} from "geojson" | ||||||
|   import SelectedElementView from "./BigComponents/SelectedElementView.svelte" |     import SelectedElementView from "./BigComponents/SelectedElementView.svelte" | ||||||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig" |     import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||||
|   import Filterview from "./BigComponents/Filterview.svelte" |     import Filterview from "./BigComponents/Filterview.svelte" | ||||||
|   import ThemeViewState from "../Models/ThemeViewState" |     import ThemeViewState from "../Models/ThemeViewState" | ||||||
|   import type {MapProperties} from "../Models/MapProperties" |     import type {MapProperties} from "../Models/MapProperties" | ||||||
|   import Geosearch from "./BigComponents/Geosearch.svelte" |     import Geosearch from "./BigComponents/Geosearch.svelte" | ||||||
|   import Translations from "./i18n/Translations" |     import Translations from "./i18n/Translations" | ||||||
|   import {CogIcon, EyeIcon, MenuIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid" |     import {CogIcon, EyeIcon, MenuIcon, XCircleIcon} 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" | ||||||
|   import FloatOver from "./Base/FloatOver.svelte" |     import FloatOver from "./Base/FloatOver.svelte" | ||||||
|   import PrivacyPolicy from "./BigComponents/PrivacyPolicy" |     import PrivacyPolicy from "./BigComponents/PrivacyPolicy" | ||||||
|   import Constants from "../Models/Constants" |     import Constants from "../Models/Constants" | ||||||
|   import TabbedGroup from "./Base/TabbedGroup.svelte" |     import TabbedGroup from "./Base/TabbedGroup.svelte" | ||||||
|   import UserRelatedState from "../Logic/State/UserRelatedState" |     import UserRelatedState from "../Logic/State/UserRelatedState" | ||||||
|   import LoginToggle from "./Base/LoginToggle.svelte" |     import LoginToggle from "./Base/LoginToggle.svelte" | ||||||
|   import LoginButton from "./Base/LoginButton.svelte" |     import LoginButton from "./Base/LoginButton.svelte" | ||||||
|   import CopyrightPanel from "./BigComponents/CopyrightPanel" |     import CopyrightPanel from "./BigComponents/CopyrightPanel" | ||||||
|   import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" |     import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" | ||||||
|   import ModalRight from "./Base/ModalRight.svelte" |     import ModalRight from "./Base/ModalRight.svelte" | ||||||
|   import {Utils} from "../Utils" |     import {Utils} from "../Utils" | ||||||
|   import Hotkeys from "./Base/Hotkeys" |     import Hotkeys from "./Base/Hotkeys" | ||||||
|   import {VariableUiElement} from "./Base/VariableUIElement" |     import {VariableUiElement} from "./Base/VariableUIElement" | ||||||
|   import SvelteUIElement from "./Base/SvelteUIElement" |     import SvelteUIElement from "./Base/SvelteUIElement" | ||||||
|   import OverlayToggle from "./BigComponents/OverlayToggle.svelte" |     import OverlayToggle from "./BigComponents/OverlayToggle.svelte" | ||||||
|   import LevelSelector from "./BigComponents/LevelSelector.svelte" |     import LevelSelector from "./BigComponents/LevelSelector.svelte" | ||||||
|   import ExtraLinkButton from "./BigComponents/ExtraLinkButton" |     import ExtraLinkButton from "./BigComponents/ExtraLinkButton" | ||||||
|   import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" |     import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" | ||||||
|   import Svg from "../Svg" |     import Svg from "../Svg" | ||||||
|   import {ShareScreen} from "./BigComponents/ShareScreen" |     import {ShareScreen} from "./BigComponents/ShareScreen" | ||||||
|   import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" |     import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" | ||||||
|   import type {RasterLayerPolygon} from "../Models/RasterLayers" |     import type {RasterLayerPolygon} from "../Models/RasterLayers" | ||||||
|   import {AvailableRasterLayers} from "../Models/RasterLayers" |     import {AvailableRasterLayers} from "../Models/RasterLayers" | ||||||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" |     import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" | ||||||
|   import IfHidden from "./Base/IfHidden.svelte" |     import IfHidden from "./Base/IfHidden.svelte" | ||||||
|   import {onDestroy} from "svelte" |     import {onDestroy} from "svelte" | ||||||
|   import {OpenJosm} from "./BigComponents/OpenJosm" |     import {OpenJosm} from "./BigComponents/OpenJosm" | ||||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte" |     import MapillaryLink from "./BigComponents/MapillaryLink.svelte" | ||||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" |     import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; |     import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; | ||||||
|  |     import Loading from "./Base/Loading.svelte"; | ||||||
|  |     import StateIndicator from "./BigComponents/StateIndicator.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let state: ThemeViewState |     export let state: ThemeViewState | ||||||
|   let layout = state.layout |     let layout = state.layout | ||||||
| 
 | 
 | ||||||
|   let maplibremap: UIEventSource<MlMap> = state.map |     let maplibremap: UIEventSource<MlMap> = state.map | ||||||
|   let selectedElement: UIEventSource<Feature> = state.selectedElement |     let selectedElement: UIEventSource<Feature> = state.selectedElement | ||||||
|   let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer |     let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer | ||||||
| 
 | 
 | ||||||
|   const selectedElementView = selectedElement.map( |     const selectedElementView = selectedElement.map( | ||||||
|     (selectedElement) => { |         (selectedElement) => { | ||||||
|       // Svelte doesn't properly reload some of the legacy UI-elements |             // Svelte doesn't properly reload some of the legacy UI-elements | ||||||
|       // As such, we _reconstruct_ the selectedElementView every time a new feature is selected |             // As such, we _reconstruct_ the selectedElementView every time a new feature is selected | ||||||
|       // This is a bit wasteful, but until everything is a svelte-component, this should do the trick |             // This is a bit wasteful, but until everything is a svelte-component, this should do the trick | ||||||
|       const layer = selectedLayer.data |             const layer = selectedLayer.data | ||||||
|       if (selectedElement === undefined || layer === undefined) { |             if (selectedElement === undefined || layer === undefined) { | ||||||
|         return undefined |                 return undefined | ||||||
|       } |             } | ||||||
| 
 | 
 | ||||||
|       if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) { |             if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) { | ||||||
|         return undefined |                 return undefined | ||||||
|       } |             } | ||||||
| 
 | 
 | ||||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id) |             const tags = state.featureProperties.getStore(selectedElement.properties.id) | ||||||
|       return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }) |             return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags}) | ||||||
|     }, |         }, | ||||||
|     [selectedLayer] |         [selectedLayer] | ||||||
|   ) |     ) | ||||||
| 
 | 
 | ||||||
|   const selectedElementTitle = selectedElement.map( |     const selectedElementTitle = selectedElement.map( | ||||||
|     (selectedElement) => { |         (selectedElement) => { | ||||||
|       // Svelte doesn't properly reload some of the legacy UI-elements |             // Svelte doesn't properly reload some of the legacy UI-elements | ||||||
|       // As such, we _reconstruct_ the selectedElementView every time a new feature is selected |             // As such, we _reconstruct_ the selectedElementView every time a new feature is selected | ||||||
|       // This is a bit wasteful, but until everything is a svelte-component, this should do the trick |             // This is a bit wasteful, but until everything is a svelte-component, this should do the trick | ||||||
|       const layer = selectedLayer.data |             const layer = selectedLayer.data | ||||||
|       if (selectedElement === undefined || layer === undefined) { |             if (selectedElement === undefined || layer === undefined) { | ||||||
|         return undefined |                 return undefined | ||||||
|       } |             } | ||||||
| 
 | 
 | ||||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id) |             const tags = state.featureProperties.getStore(selectedElement.properties.id) | ||||||
|       return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags }) |             return new SvelteUIElement(SelectedElementTitle, {state, layer, selectedElement, tags}) | ||||||
|     }, |         }, | ||||||
|     [selectedLayer] |         [selectedLayer] | ||||||
|   ) |     ) | ||||||
| 
 | 
 | ||||||
|   let mapproperties: MapProperties = state.mapProperties |     let mapproperties: MapProperties = state.mapProperties | ||||||
|   let featureSwitches: FeatureSwitchState = state.featureSwitches |     let featureSwitches: FeatureSwitchState = state.featureSwitches | ||||||
|   let availableLayers = state.availableLayers |     let availableLayers = state.availableLayers | ||||||
|   let userdetails = state.osmConnection.userDetails |     let userdetails = state.osmConnection.userDetails | ||||||
|   let currentViewLayer = layout.layers.find((l) => l.id === "current_view") |     let currentViewLayer = layout.layers.find((l) => l.id === "current_view") | ||||||
|   let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer |     let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer | ||||||
|   let rasterLayerName = |     let rasterLayerName = | ||||||
|     rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maplibre.properties.name |         rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maplibre.properties.name | ||||||
|   onDestroy( |     onDestroy( | ||||||
|     rasterLayer.addCallbackAndRunD((l) => { |         rasterLayer.addCallbackAndRunD((l) => { | ||||||
|       rasterLayerName = l.properties.name |             rasterLayerName = l.properties.name | ||||||
|     }) |         }) | ||||||
|   ) |     ) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> | <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> | ||||||
|   <MaplibreMap map={maplibremap} /> |     <MaplibreMap map={maplibremap}/> | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| <div class="pointer-events-none absolute top-0 left-0 w-full"> | <div class="pointer-events-none absolute top-0 left-0 w-full"> | ||||||
|   <!-- Top components --> |     <!-- Top components --> | ||||||
|   <If condition={state.featureSwitches.featureSwitchSearch}> |     <If condition={state.featureSwitches.featureSwitchSearch}> | ||||||
|     <div class="pointer-events-auto float-right mt-1 px-1 max-[480px]:w-full sm:m-2"> |         <div class="pointer-events-auto float-right mt-1 px-1 max-[480px]:w-full sm:m-2"> | ||||||
|       <Geosearch |             <Geosearch | ||||||
|         bounds={state.mapProperties.bounds} |                     bounds={state.mapProperties.bounds} | ||||||
|         perLayer={state.perLayer} |                     perLayer={state.perLayer} | ||||||
|         {selectedElement} |                     {selectedElement} | ||||||
|         {selectedLayer} |                     {selectedLayer} | ||||||
|       /> |             /> | ||||||
|     </div> |         </div> | ||||||
|   </If> |     </If> | ||||||
|   <div class="float-left m-1 flex flex-col sm:mt-2"> |     <div class="float-left m-1 flex flex-col sm:mt-2"> | ||||||
|     <MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}> |         <MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}> | ||||||
|       <div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2"> |             <div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2"> | ||||||
|         <img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon} /> |                 <img class="mr-0.5 block h-6 w-6 sm:mr-1 md:mr-2 md:h-8 md:w-8" src={layout.icon}/> | ||||||
|         <b class="mr-1"> |                 <b class="mr-1"> | ||||||
|           <Tr t={layout.title} /> |                     <Tr t={layout.title}/> | ||||||
|         </b> |                 </b> | ||||||
|       </div> |             </div> | ||||||
|     </MapControlButton> |         </MapControlButton> | ||||||
|     <MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)}> |         <MapControlButton on:click={() => state.guistate.menuIsOpened.setData(true)}> | ||||||
|       <MenuIcon class="h-8 w-8 cursor-pointer" /> |             <MenuIcon class="h-8 w-8 cursor-pointer"/> | ||||||
|     </MapControlButton> |         </MapControlButton> | ||||||
|     {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} |         {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} | ||||||
|       <MapControlButton |             <MapControlButton | ||||||
|         on:click={() => { |                     on:click={() => { | ||||||
|           selectedLayer.setData(currentViewLayer) |           selectedLayer.setData(currentViewLayer) | ||||||
|           selectedElement.setData(state.currentView.features?.data?.[0]) |           selectedElement.setData(state.currentView.features?.data?.[0]) | ||||||
|         }} |         }} | ||||||
|       > |             > | ||||||
|  |                 <ToSvelte | ||||||
|  |                         construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")} | ||||||
|  |                 /> | ||||||
|  |             </MapControlButton> | ||||||
|  |         {/if} | ||||||
|         <ToSvelte |         <ToSvelte | ||||||
|           construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")} |                 construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")} | ||||||
|         /> |         /> | ||||||
|       </MapControlButton> |         <If condition={state.featureSwitchIsTesting}> | ||||||
|     {/if} |             <div class="alert w-fit">Testmode</div> | ||||||
|     <ToSvelte |         </If> | ||||||
|       construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")} |     </div> | ||||||
|     /> |     <div class="flex justify-center w-full"> <!-- Flex and w-full are needed for the positioning --> | ||||||
|     <If condition={state.featureSwitchIsTesting}> |         <!-- Centermessage --> | ||||||
|       <div class="alert w-fit">Testmode</div> |         <StateIndicator {state}/> | ||||||
|     </If> |     </div> | ||||||
|   </div> |  | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen"> | <div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen"> | ||||||
|   <div class="flex w-full items-end justify-between px-4"> |     <!-- bottom controls --> | ||||||
|     <div class="flex"> |     <div class="flex w-full items-end justify-between px-4"> | ||||||
|       <!-- bottom left elements --> |         <div class="flex"> | ||||||
|       <OpenBackgroundSelectorButton {state} hideTooltip={true}/> |             <!-- bottom left elements --> | ||||||
|       <a |             <OpenBackgroundSelectorButton hideTooltip={true} {state}/> | ||||||
|         class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100" |             <a | ||||||
|         on:click={() => { |                     class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100" | ||||||
|  |                     on:click={() => { | ||||||
|           state.guistate.themeViewTab.setData("copyright") |           state.guistate.themeViewTab.setData("copyright") | ||||||
|           state.guistate.themeIsOpened.setData(true) |           state.guistate.themeIsOpened.setData(true) | ||||||
|         }} |         }} | ||||||
|       > |             > | ||||||
|         © OpenStreetMap, <span class="w-24">{rasterLayerName}</span> |                 © OpenStreetMap, <span class="w-24">{rasterLayerName}</span> | ||||||
|       </a> |             </a> | ||||||
|     </div> |  | ||||||
| 
 |  | ||||||
|     <div class="flex flex-col items-end"> |  | ||||||
|       <!-- bottom right elements --> |  | ||||||
|       <If condition={state.floors.map((f) => f.length > 1)}> |  | ||||||
|         <div class="pointer-events-auto mr-0.5"> |  | ||||||
|           <LevelSelector |  | ||||||
|             floors={state.floors} |  | ||||||
|             layerState={state.layerState} |  | ||||||
|             zoom={state.mapProperties.zoom} |  | ||||||
|           /> |  | ||||||
|         </div> |         </div> | ||||||
|       </If> | 
 | ||||||
|       <MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}> |         <div class="flex flex-col items-end"> | ||||||
|         <ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")} /> |             <!-- bottom right elements --> | ||||||
|       </MapControlButton> |             <If condition={state.floors.map((f) => f.length > 1)}> | ||||||
|       <MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}> |                 <div class="pointer-events-auto mr-0.5"> | ||||||
|         <ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")} /> |                     <LevelSelector | ||||||
|       </MapControlButton> |                             floors={state.floors} | ||||||
|       <If condition={featureSwitches.featureSwitchGeolocation}> |                             layerState={state.layerState} | ||||||
|         <MapControlButton> |                             zoom={state.mapProperties.zoom} | ||||||
|           <ToSvelte |                     /> | ||||||
|             construct={new GeolocationControl(state.geolocation, mapproperties).SetClass( |                 </div> | ||||||
|  |             </If> | ||||||
|  |             <MapControlButton on:click={() => mapproperties.zoom.update((z) => z + 1)}> | ||||||
|  |                 <ToSvelte construct={Svg.plus_svg().SetClass("w-8 h-8")}/> | ||||||
|  |             </MapControlButton> | ||||||
|  |             <MapControlButton on:click={() => mapproperties.zoom.update((z) => z - 1)}> | ||||||
|  |                 <ToSvelte construct={Svg.min_svg().SetClass("w-8 h-8")}/> | ||||||
|  |             </MapControlButton> | ||||||
|  |             <If condition={featureSwitches.featureSwitchGeolocation}> | ||||||
|  |                 <MapControlButton> | ||||||
|  |                     <ToSvelte | ||||||
|  |                             construct={new GeolocationControl(state.geolocation, mapproperties).SetClass( | ||||||
|               "block w-8 h-8" |               "block w-8 h-8" | ||||||
|             )} |             )} | ||||||
|           /> |                     /> | ||||||
|         </MapControlButton> |                 </MapControlButton> | ||||||
|       </If> |             </If> | ||||||
|  |         </div> | ||||||
|     </div> |     </div> | ||||||
|   </div> |  | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
| <If | <If | ||||||
|   condition={selectedElementView.map( |         condition={selectedElementView.map( | ||||||
|     (v) => |     (v) => | ||||||
|       v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover, |       v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover, | ||||||
|     [selectedLayer] |     [selectedLayer] | ||||||
|   )} |   )} | ||||||
| > | > | ||||||
|   <ModalRight |     <!-- right modal with the selected element view --> | ||||||
|     on:close={() => { |     <ModalRight | ||||||
|  |             on:close={() => { | ||||||
|       selectedElement.setData(undefined) |       selectedElement.setData(undefined) | ||||||
|     }} |     }} | ||||||
|   > |     > | ||||||
|     <div class="normal-background absolute flex h-full w-full flex-col"> |         <div class="normal-background absolute flex h-full w-full flex-col"> | ||||||
|       <ToSvelte construct={new VariableUiElement(selectedElementTitle)}> |             <ToSvelte construct={new VariableUiElement(selectedElementTitle)}> | ||||||
|         <!-- Title --> |                 <!-- Title --> | ||||||
|       </ToSvelte> |             </ToSvelte> | ||||||
|       <ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}> |             <ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}> | ||||||
|         <!-- Main view --> |                 <!-- Main view --> | ||||||
|       </ToSvelte> |             </ToSvelte> | ||||||
|     </div> |         </div> | ||||||
|   </ModalRight> |     </ModalRight> | ||||||
| </If> | </If> | ||||||
| 
 | 
 | ||||||
| <If | <If | ||||||
|   condition={selectedElementView.map( |         condition={selectedElementView.map( | ||||||
|     (v) => |     (v) => | ||||||
|       v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover, |       v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover, | ||||||
|     [selectedLayer] |     [selectedLayer] | ||||||
|   )} |   )} | ||||||
| > | > | ||||||
|   <FloatOver |     <!-- Floatover with the selected element, if applicable --> | ||||||
|     on:close={() => { |     <FloatOver | ||||||
|  |             on:close={() => { | ||||||
|       selectedElement.setData(undefined) |       selectedElement.setData(undefined) | ||||||
|     }} |     }} | ||||||
|   > |     > | ||||||
|     <ToSvelte construct={new VariableUiElement(selectedElementView)} /> |         <ToSvelte construct={new VariableUiElement(selectedElementView)}/> | ||||||
|   </FloatOver> |     </FloatOver> | ||||||
| </If> | </If> | ||||||
| 
 | 
 | ||||||
| <If condition={state.guistate.themeIsOpened}> | <If condition={state.guistate.themeIsOpened}> | ||||||
|   <!-- Theme menu --> |     <!-- Theme menu --> | ||||||
|   <FloatOver> |     <FloatOver> | ||||||
|     <span slot="close-button"><!-- Disable the close button --></span> |         <span slot="close-button"><!-- Disable the close button --></span> | ||||||
|     <TabbedGroup tab={state.guistate.themeViewTabIndex}> |         <TabbedGroup tab={state.guistate.themeViewTabIndex}> | ||||||
|       <div slot="post-tablist"> |             <div slot="post-tablist"> | ||||||
|         <XCircleIcon |                 <XCircleIcon | ||||||
|           class="mr-2 h-8 w-8" |                         class="mr-2 h-8 w-8" | ||||||
|           on:click={() => state.guistate.themeIsOpened.setData(false)} |                         on:click={() => state.guistate.themeIsOpened.setData(false)} | ||||||
|         /> |                 /> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="flex" slot="title0"> |             <div class="flex" slot="title0"> | ||||||
|         <img class="block h-4 w-4" src={layout.icon} /> |                 <img class="block h-4 w-4" src={layout.icon}/> | ||||||
|         <Tr t={layout.title} /> |                 <Tr t={layout.title}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="m-4" slot="content0"> |             <div class="m-4" slot="content0"> | ||||||
|         <ThemeIntroPanel {state} /> |                 <ThemeIntroPanel {state}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="flex" slot="title1"> |             <div class="flex" slot="title1"> | ||||||
|         <If condition={state.featureSwitches.featureSwitchFilter}> |                 <If condition={state.featureSwitches.featureSwitchFilter}> | ||||||
|           <ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")} /> |                     <ToSvelte construct={Svg.filter_svg().SetClass("w-4 h-4")}/> | ||||||
|           <Tr t={Translations.t.general.menu.filter} /> |                     <Tr t={Translations.t.general.menu.filter}/> | ||||||
|         </If> |                 </If> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="m-2 flex flex-col" slot="content1"> |             <div class="m-2 flex flex-col" slot="content1"> | ||||||
|         {#each layout.layers as layer} |                 {#each layout.layers as layer} | ||||||
|           <Filterview |                     <Filterview | ||||||
|             zoomlevel={state.mapProperties.zoom} |                             zoomlevel={state.mapProperties.zoom} | ||||||
|             filteredLayer={state.layerState.filteredLayers.get(layer.id)} |                             filteredLayer={state.layerState.filteredLayers.get(layer.id)} | ||||||
|             highlightedLayer={state.guistate.highlightedLayerInFilters} |                             highlightedLayer={state.guistate.highlightedLayerInFilters} | ||||||
|           /> |                     /> | ||||||
|         {/each} |                 {/each} | ||||||
|         {#each layout.tileLayerSources as tilesource} |                 {#each layout.tileLayerSources as tilesource} | ||||||
|           <OverlayToggle |                     <OverlayToggle | ||||||
|             layerproperties={tilesource} |                             layerproperties={tilesource} | ||||||
|             state={state.overlayLayerStates.get(tilesource.id)} |                             state={state.overlayLayerStates.get(tilesource.id)} | ||||||
|             highlightedLayer={state.guistate.highlightedLayerInFilters} |                             highlightedLayer={state.guistate.highlightedLayerInFilters} | ||||||
|             zoomlevel={state.mapProperties.zoom} |                             zoomlevel={state.mapProperties.zoom} | ||||||
|           /> |                     /> | ||||||
|         {/each} |                 {/each} | ||||||
|       </div> |             </div> | ||||||
|       <div class="flex" slot="title2"> |             <div class="flex" slot="title2"> | ||||||
|         <If condition={state.featureSwitches.featureSwitchEnableExport}> |                 <If condition={state.featureSwitches.featureSwitchEnableExport}> | ||||||
|           <ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")} /> |                     <ToSvelte construct={Svg.download_svg().SetClass("w-4 h-4")}/> | ||||||
|           <Tr t={Translations.t.general.download.title} /> |                     <Tr t={Translations.t.general.download.title}/> | ||||||
|         </If> |                 </If> | ||||||
|       </div> |             </div> | ||||||
|       <div class="m-4" slot="content2"> |             <div class="m-4" slot="content2"> | ||||||
|         <DownloadPanel {state} /> |                 <DownloadPanel {state}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div slot="title3"> |             <div slot="title3"> | ||||||
|         <Tr t={Translations.t.general.attribution.title} /> |                 <Tr t={Translations.t.general.attribution.title}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" /> |             <ToSvelte construct={() => new CopyrightPanel(state)} slot="content3"/> | ||||||
| 
 | 
 | ||||||
|       <div slot="title4"> |             <div slot="title4"> | ||||||
|         <Tr t={Translations.t.general.sharescreen.title} /> |                 <Tr t={Translations.t.general.sharescreen.title}/> | ||||||
|       </div> |             </div> | ||||||
|       <div class="m-2" slot="content4"> |             <div class="m-2" slot="content4"> | ||||||
|         <ToSvelte construct={() => new ShareScreen(state)} /> |                 <ToSvelte construct={() => new ShareScreen(state)}/> | ||||||
|       </div> |             </div> | ||||||
|     </TabbedGroup> |         </TabbedGroup> | ||||||
|   </FloatOver> |     </FloatOver> | ||||||
| </If> | </If> | ||||||
| 
 | 
 | ||||||
| <IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}> | <IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}> | ||||||
|   <!-- background layer selector --> |     <!-- background layer selector --> | ||||||
|   <FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}> |     <FloatOver on:close={() => state.guistate.backgroundLayerSelectionIsOpened.setData(false)}> | ||||||
|     <div class="h-full p-2"> |         <div class="h-full p-2"> | ||||||
|       <RasterLayerOverview |             <RasterLayerOverview | ||||||
|         userstate={state.userRelatedState} |                     {availableLayers} | ||||||
|         mapproperties={state.mapProperties} |                     map={state.map} | ||||||
|         map={state.map} |                     mapproperties={state.mapProperties} | ||||||
|         {availableLayers} |                     userstate={state.userRelatedState} | ||||||
|         visible={state.guistate.backgroundLayerSelectionIsOpened} |                     visible={state.guistate.backgroundLayerSelectionIsOpened} | ||||||
|       /> |             /> | ||||||
|     </div> |         </div> | ||||||
|   </FloatOver> |     </FloatOver> | ||||||
| </IfHidden> | </IfHidden> | ||||||
| 
 | 
 | ||||||
| <If condition={state.guistate.menuIsOpened}> | <If condition={state.guistate.menuIsOpened}> | ||||||
|   <!-- Menu page --> |     <!-- Menu page --> | ||||||
|   <FloatOver> |     <FloatOver> | ||||||
|     <span slot="close-button"><!-- Hide the default close button --></span> |         <span slot="close-button"><!-- Hide the default close button --></span> | ||||||
|     <TabbedGroup tab={state.guistate.menuViewTabIndex}> |         <TabbedGroup tab={state.guistate.menuViewTabIndex}> | ||||||
|       <div slot="post-tablist"> |             <div slot="post-tablist"> | ||||||
|         <XCircleIcon |                 <XCircleIcon | ||||||
|           class="mr-2 h-8 w-8" |                         class="mr-2 h-8 w-8" | ||||||
|           on:click={() => state.guistate.menuIsOpened.setData(false)} |                         on:click={() => state.guistate.menuIsOpened.setData(false)} | ||||||
|         /> |                 /> | ||||||
|       </div> |             </div> | ||||||
|       <div class="flex" slot="title0"> |             <div class="flex" slot="title0"> | ||||||
|         <Tr t={Translations.t.general.menu.aboutMapComplete} /> |                 <Tr t={Translations.t.general.menu.aboutMapComplete}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="links-as-button links-w-full m-2 flex flex-col gap-y-1" slot="content0"> |             <div class="links-as-button links-w-full m-2 flex flex-col gap-y-1" slot="content0"> | ||||||
|         <Tr t={Translations.t.general.aboutMapComplete.intro} /> |                 <Tr t={Translations.t.general.aboutMapComplete.intro}/> | ||||||
| 
 | 
 | ||||||
|         <a class="flex" href={Utils.HomepageLink()}> |                 <a class="flex" href={Utils.HomepageLink()}> | ||||||
|           <img class="h-6 w-6" src="./assets/svg/add.svg" /> |                     <img class="h-6 w-6" src="./assets/svg/add.svg"/> | ||||||
|           <Tr t={Translations.t.general.backToIndex} /> |                     <Tr t={Translations.t.general.backToIndex}/> | ||||||
|         </a> |                 </a> | ||||||
| 
 | 
 | ||||||
|         <a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank"> |                 <a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank"> | ||||||
|           <img class="h-6 w-6" src="./assets/svg/bug.svg" /> |                     <img class="h-6 w-6" src="./assets/svg/bug.svg"/> | ||||||
|           <Tr t={Translations.t.general.attribution.openIssueTracker} /> |                     <Tr t={Translations.t.general.attribution.openIssueTracker}/> | ||||||
|         </a> |                 </a> | ||||||
| 
 | 
 | ||||||
|         <a class="flex" href="https://en.osm.town/@MapComplete" target="_blank"> |                 <a class="flex" href="https://en.osm.town/@MapComplete" target="_blank"> | ||||||
|           <img class="h-6 w-6" src="./assets/svg/mastodon.svg" /> |                     <img class="h-6 w-6" src="./assets/svg/mastodon.svg"/> | ||||||
|           <Tr t={Translations.t.general.attribution.followOnMastodon} /> |                     <Tr t={Translations.t.general.attribution.followOnMastodon}/> | ||||||
|         </a> |                 </a> | ||||||
| 
 | 
 | ||||||
|         <a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank"> |                 <a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank"> | ||||||
|           <img class="h-6 w-6" src="./assets/svg/liberapay.svg" /> |                     <img class="h-6 w-6" src="./assets/svg/liberapay.svg"/> | ||||||
|           <Tr t={Translations.t.general.attribution.donate} /> |                     <Tr t={Translations.t.general.attribution.donate}/> | ||||||
|         </a> |                 </a> | ||||||
| 
 | 
 | ||||||
|         <a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank"> |                 <a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank"> | ||||||
|           <img class="h-6 w-6" src="./assets/svg/statistics.svg" /> |                     <img class="h-6 w-6" src="./assets/svg/statistics.svg"/> | ||||||
|           <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })} /> |                     <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: "MapComplete" })}/> | ||||||
|         </a> |                 </a> | ||||||
|         {Constants.vNumber} |                 {Constants.vNumber} | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="flex" slot="title1"> |             <div class="flex" slot="title1"> | ||||||
|         <CogIcon class="h-6 w-6" /> |                 <CogIcon class="h-6 w-6"/> | ||||||
|         <Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} /> |                 <Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="links-as-button" slot="content1"> |             <div class="links-as-button" slot="content1"> | ||||||
|         <!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it --> |                 <!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it --> | ||||||
|         <LoginToggle {state}> |                 <LoginToggle {state}> | ||||||
|           <div class="flex flex-col" slot="not-logged-in"> |                     <div class="flex flex-col" slot="not-logged-in"> | ||||||
|             <Tr class="alert" t={Translations.t.userinfo.notLoggedIn} /> |                         <Tr class="alert" t={Translations.t.userinfo.notLoggedIn}/> | ||||||
|             <LoginButton clss="primary" osmConnection={state.osmConnection} /> |                         <LoginButton clss="primary" osmConnection={state.osmConnection}/> | ||||||
|           </div> |                     </div> | ||||||
|           <SelectedElementView |                     <SelectedElementView | ||||||
|             highlightedRendering={state.guistate.highlightedUserSetting} |                             highlightedRendering={state.guistate.highlightedUserSetting} | ||||||
|             layer={UserRelatedState.usersettingsConfig} |                             layer={UserRelatedState.usersettingsConfig} | ||||||
|             selectedElement={{ |                             selectedElement={{ | ||||||
|               type: "Feature", |               type: "Feature", | ||||||
|               properties: {}, |               properties: {}, | ||||||
|               geometry: { type: "Point", coordinates: [0, 0] }, |               geometry: { type: "Point", coordinates: [0, 0] }, | ||||||
|             }} |             }} | ||||||
|             {state} |                             {state} | ||||||
|             tags={state.userRelatedState.preferencesAsTags} |                             tags={state.userRelatedState.preferencesAsTags} | ||||||
|           /> |                     /> | ||||||
|         </LoginToggle> |                 </LoginToggle> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="flex" slot="title2"> |             <div class="flex" slot="title2"> | ||||||
|         <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")} /> |                 <ToSvelte construct={Svg.community_svg().SetClass("w-6 h-6")}/> | ||||||
|         Get in touch with others |                 Get in touch with others | ||||||
|       </div> |             </div> | ||||||
|       <div class="m-2" slot="content2"> |             <div class="m-2" slot="content2"> | ||||||
|         <CommunityIndexView location={state.mapProperties.location} /> |                 <CommunityIndexView location={state.mapProperties.location}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <div class="flex" slot="title3"> |             <div class="flex" slot="title3"> | ||||||
|         <EyeIcon class="w-6" /> |                 <EyeIcon class="w-6"/> | ||||||
|         <Tr t={Translations.t.privacy.title} /> |                 <Tr t={Translations.t.privacy.title}/> | ||||||
|       </div> |             </div> | ||||||
|       <div class="m-2" slot="content3"> |             <div class="m-2" slot="content3"> | ||||||
|         <ToSvelte construct={() => new PrivacyPolicy()} /> |                 <ToSvelte construct={() => new PrivacyPolicy()}/> | ||||||
|       </div> |             </div> | ||||||
| 
 | 
 | ||||||
|       <Tr slot="title4" t={Translations.t.advanced.title} /> |             <Tr slot="title4" t={Translations.t.advanced.title}/> | ||||||
|       <div class="m-2 flex flex-col" slot="content4"> |             <div class="m-2 flex flex-col" slot="content4"> | ||||||
|         <OpenIdEditor mapProperties={state.mapProperties} /> |                 <OpenIdEditor mapProperties={state.mapProperties}/> | ||||||
|         <ToSvelte |                 <ToSvelte | ||||||
|           construct={() => |                         construct={() => | ||||||
|             new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")} |             new OpenJosm(state.osmConnection, state.mapProperties.bounds).SetClass("w-full")} | ||||||
|         /> |                 /> | ||||||
|         <MapillaryLink mapProperties={state.mapProperties} /> |                 <MapillaryLink mapProperties={state.mapProperties}/> | ||||||
|         <ToSvelte construct={Hotkeys.generateDocumentationDynamic} /> |                 <ToSvelte construct={Hotkeys.generateDocumentationDynamic}/> | ||||||
|       </div> |             </div> | ||||||
|     </TabbedGroup> |         </TabbedGroup> | ||||||
|   </FloatOver> |     </FloatOver> | ||||||
| </If> | </If> | ||||||
|  |  | ||||||
|  | @ -3,7 +3,9 @@ | ||||||
|         "title": "Advanced features" |         "title": "Advanced features" | ||||||
|     }, |     }, | ||||||
|     "centerMessage": { |     "centerMessage": { | ||||||
|  |         "allFilteredAway": "No feature in view meets all filters", | ||||||
|         "loadingData": "Loading data…", |         "loadingData": "Loading data…", | ||||||
|  |         "noData": "There are no relevant features in the current view", | ||||||
|         "ready": "Done!", |         "ready": "Done!", | ||||||
|         "retrying": "Loading data failed. Trying again in {count} seconds…", |         "retrying": "Loading data failed. Trying again in {count} seconds…", | ||||||
|         "zoomIn": "Zoom in to view or edit the data" |         "zoomIn": "Zoom in to view or edit the data" | ||||||
|  | @ -400,7 +402,7 @@ | ||||||
|         "geolocate": "Pan the map to the current location or zoom the map to the current location. Requests geopermission", |         "geolocate": "Pan the map to the current location or zoom the map to the current location. Requests geopermission", | ||||||
|         "intro": "MapComplete supports the following keys:", |         "intro": "MapComplete supports the following keys:", | ||||||
|         "key": "Key combination", |         "key": "Key combination", | ||||||
|         "openLayersPanel": "Opens the Background, layers and filters panel", |         "openLayersPanel": "Opens the layers and filters panel", | ||||||
|         "selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers", |         "selectAerial": "Set the background to aerial or satellite imagery. Toggles between the two best, available layers", | ||||||
|         "selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers", |         "selectMap": "Set the background to a map from external sources. Toggles between the two best, available layers", | ||||||
|         "selectMapnik": "Set the background layer to OpenStreetMap-carto", |         "selectMapnik": "Set the background layer to OpenStreetMap-carto", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue