forked from MapComplete/MapComplete
		
	UX: place 'add new button' in bottom-left instead of having a dynamic pin
This commit is contained in:
		
							parent
							
								
									dab8d6cd5d
								
							
						
					
					
						commit
						285fe9ab83
					
				
					 7 changed files with 730 additions and 703 deletions
				
			
		|  | @ -124,7 +124,7 @@ | |||
|             "pleaseLogin": "Please log in to add a new feature", | ||||
|             "presetInfo": "The new POI will have {tags}", | ||||
|             "stillLoading": "The data is still loading. Please wait a bit before you add a new feature.", | ||||
|             "title": "Add a new feature?", | ||||
|             "title": "Add a new feature", | ||||
|             "warnVisibleForEveryone": "Your addition will be visible for everyone", | ||||
|             "wrongType": "This feature is not a node or a way and can not be imported", | ||||
|             "zoomInFurther": "Zoom in further to add a feature.", | ||||
|  |  | |||
|  | @ -124,7 +124,7 @@ | |||
|             "pleaseLogin": "Gelieve je aan te melden om een object toe te voegen", | ||||
|             "presetInfo": "Het nieuwe object krijgt de attributen {tags}", | ||||
|             "stillLoading": "De data worden nog geladen. Nog even geduld en dan kan je een object toevoegen.", | ||||
|             "title": "Nieuw object toevoegen?", | ||||
|             "title": "Nieuw object toevoegen", | ||||
|             "warnVisibleForEveryone": "Je toevoeging is voor iedereen zichtbaar", | ||||
|             "wrongType": "Dit object is geen punt of lijn en kan daarom niet geïmporteerd worden", | ||||
|             "zoomInFurther": "Gelieve verder in te zoomen om een object toe te voegen.", | ||||
|  |  | |||
|  | @ -1,10 +1,11 @@ | |||
| import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" | ||||
| import { WritableFeatureSource } from "../FeatureSource" | ||||
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | ||||
| import { Feature, Point } from "geojson" | ||||
| import { TagUtils } from "../../Tags/TagUtils" | ||||
| import BaseUIElement from "../../../UI/BaseUIElement" | ||||
| import { Utils } from "../../../Utils" | ||||
| import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig"; | ||||
| import { WritableFeatureSource } from "../FeatureSource"; | ||||
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"; | ||||
| import { Feature, Point } from "geojson"; | ||||
| import { TagUtils } from "../../Tags/TagUtils"; | ||||
| import BaseUIElement from "../../../UI/BaseUIElement"; | ||||
| import { Utils } from "../../../Utils"; | ||||
| import { OsmTags } from "../../../Models/OsmFeature"; | ||||
| 
 | ||||
| /** | ||||
|  * Highly specialized feature source. | ||||
|  | @ -12,8 +13,14 @@ import { Utils } from "../../../Utils" | |||
|  */ | ||||
| export class LastClickFeatureSource implements WritableFeatureSource { | ||||
|     public readonly features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]) | ||||
|     private i: number = 0 | ||||
|     private readonly hasNoteLayer: string | ||||
|     private readonly renderings: string[]; | ||||
|     private readonly hasPresets: string; | ||||
| 
 | ||||
|     constructor(location: Store<{ lon: number; lat: number }>, layout: LayoutConfig) { | ||||
|         this.hasNoteLayer = layout.layers.some((l) => l.id === "note") ? "yes" : "no" | ||||
|         this.hasPresets=  layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no" | ||||
|         const allPresets: BaseUIElement[] = [] | ||||
|         for (const layer of layout.layers) | ||||
|             for (let i = 0; i < (layer.presets ?? []).length; i++) { | ||||
|  | @ -26,35 +33,36 @@ export class LastClickFeatureSource implements WritableFeatureSource { | |||
|                 allPresets.push(html) | ||||
|             } | ||||
| 
 | ||||
|         const renderings = Utils.Dedup( | ||||
|         this.renderings = Utils.Dedup( | ||||
|             allPresets.map((uiElem) => | ||||
|                 Utils.runningFromConsole ? "" : uiElem.ConstructElement().innerHTML | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         let i = 0 | ||||
| 
 | ||||
|         location.addCallbackAndRunD(({ lon, lat }) => { | ||||
|             const properties = { | ||||
|                 lastclick: "yes", | ||||
|                 id: "last_click_" + i, | ||||
|                 has_note_layer: layout.layers.some((l) => l.id === "note") ? "yes" : "no", | ||||
|                 has_presets: layout.layers.some((l) => l.presets?.length > 0) ? "yes" : "no", | ||||
|                 renderings: renderings.join(""), | ||||
|                 number_of_presets: "" + renderings.length, | ||||
|                 first_preset: renderings[0], | ||||
|             } | ||||
|             i++ | ||||
| 
 | ||||
|             const point = <Feature<Point>>{ | ||||
|                 type: "Feature", | ||||
|                 properties, | ||||
|                 geometry: { | ||||
|                     type: "Point", | ||||
|                     coordinates: [lon, lat], | ||||
|                 }, | ||||
|             } | ||||
|             this.features.setData([point]) | ||||
|             this.features.setData([this.createFeature(lon, lat)]) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public createFeature(lon: number, lat: number): Feature<Point, OsmTags> { | ||||
|         const properties: OsmTags = { | ||||
|             lastclick: "yes", | ||||
|             id: "last_click_" + this.i, | ||||
|             has_note_layer: this.hasNoteLayer , | ||||
|             has_presets:this.hasPresets , | ||||
|             renderings: this.renderings.join(""), | ||||
|             number_of_presets: "" +this. renderings.length, | ||||
|             first_preset: this.renderings[0], | ||||
|         } | ||||
|        this. i++ | ||||
| 
 | ||||
|         return <Feature<Point, OsmTags>>{ | ||||
|             type: "Feature", | ||||
|             properties, | ||||
|             geometry: { | ||||
|                 type: "Point", | ||||
|                 coordinates: [lon, lat], | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -175,7 +175,7 @@ export default class ThemeViewStateHashActor { | |||
|     } | ||||
| 
 | ||||
|     private back() { | ||||
|         console.log("Got a back event") | ||||
|         console.trace("Got a back event") | ||||
|         const state = this._state | ||||
|         // history.pushState(null, null, window.location.pathname);
 | ||||
|         if (state.selectedElement.data) { | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ export default class Constants { | |||
| 
 | ||||
|         importHelperUnlock: 5000, | ||||
|     } | ||||
|     static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 18 : 19 | ||||
|     static readonly minZoomLevelToAddNewPoint = Constants.isRetina() ? 17 : 18 | ||||
|     /** | ||||
|      * Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes. | ||||
|      * (Note that pendingChanges might upload sooner if the popup is closed or similar) | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -1,115 +1,114 @@ | |||
| <script lang="ts"> | ||||
|   import { Store, UIEventSource } from "../Logic/UIEventSource" | ||||
|   import { Map as MlMap } from "maplibre-gl" | ||||
|   import MaplibreMap from "./Map/MaplibreMap.svelte" | ||||
|   import FeatureSwitchState from "../Logic/State/FeatureSwitchState" | ||||
|   import MapControlButton from "./Base/MapControlButton.svelte" | ||||
|   import ToSvelte from "./Base/ToSvelte.svelte" | ||||
|   import If from "./Base/If.svelte" | ||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl" | ||||
|   import type { Feature } from "geojson" | ||||
|   import SelectedElementView from "./BigComponents/SelectedElementView.svelte" | ||||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
|   import Filterview from "./BigComponents/Filterview.svelte" | ||||
|   import ThemeViewState from "../Models/ThemeViewState" | ||||
|   import type { MapProperties } from "../Models/MapProperties" | ||||
|   import Geosearch from "./BigComponents/Geosearch.svelte" | ||||
|   import Translations from "./i18n/Translations" | ||||
|   import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { Store, UIEventSource } from "../Logic/UIEventSource"; | ||||
|   import { Map as MlMap } from "maplibre-gl"; | ||||
|   import MaplibreMap from "./Map/MaplibreMap.svelte"; | ||||
|   import FeatureSwitchState from "../Logic/State/FeatureSwitchState"; | ||||
|   import MapControlButton from "./Base/MapControlButton.svelte"; | ||||
|   import ToSvelte from "./Base/ToSvelte.svelte"; | ||||
|   import If from "./Base/If.svelte"; | ||||
|   import { GeolocationControl } from "./BigComponents/GeolocationControl"; | ||||
|   import type { Feature } from "geojson"; | ||||
|   import SelectedElementView from "./BigComponents/SelectedElementView.svelte"; | ||||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||
|   import Filterview from "./BigComponents/Filterview.svelte"; | ||||
|   import ThemeViewState from "../Models/ThemeViewState"; | ||||
|   import type { MapProperties } from "../Models/MapProperties"; | ||||
|   import Geosearch from "./BigComponents/Geosearch.svelte"; | ||||
|   import Translations from "./i18n/Translations"; | ||||
|   import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"; | ||||
| 
 | ||||
|   import Tr from "./Base/Tr.svelte" | ||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte" | ||||
|   import FloatOver from "./Base/FloatOver.svelte" | ||||
|   import PrivacyPolicy from "./BigComponents/PrivacyPolicy" | ||||
|   import Constants from "../Models/Constants" | ||||
|   import TabbedGroup from "./Base/TabbedGroup.svelte" | ||||
|   import UserRelatedState from "../Logic/State/UserRelatedState" | ||||
|   import LoginToggle from "./Base/LoginToggle.svelte" | ||||
|   import LoginButton from "./Base/LoginButton.svelte" | ||||
|   import CopyrightPanel from "./BigComponents/CopyrightPanel" | ||||
|   import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte" | ||||
|   import ModalRight from "./Base/ModalRight.svelte" | ||||
|   import { Utils } from "../Utils" | ||||
|   import Hotkeys from "./Base/Hotkeys" | ||||
|   import { VariableUiElement } from "./Base/VariableUIElement" | ||||
|   import SvelteUIElement from "./Base/SvelteUIElement" | ||||
|   import OverlayToggle from "./BigComponents/OverlayToggle.svelte" | ||||
|   import LevelSelector from "./BigComponents/LevelSelector.svelte" | ||||
|   import ExtraLinkButton from "./BigComponents/ExtraLinkButton" | ||||
|   import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte" | ||||
|   import Svg from "../Svg" | ||||
|   import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte" | ||||
|   import type { RasterLayerPolygon } from "../Models/RasterLayers" | ||||
|   import { AvailableRasterLayers } from "../Models/RasterLayers" | ||||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte" | ||||
|   import IfHidden from "./Base/IfHidden.svelte" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import { OpenJosm } from "./BigComponents/OpenJosm" | ||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte" | ||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte" | ||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte" | ||||
|   import StateIndicator from "./BigComponents/StateIndicator.svelte" | ||||
|   import LanguagePicker from "./LanguagePicker" | ||||
|   import Locale from "./i18n/Locale" | ||||
|   import ShareScreen from "./BigComponents/ShareScreen.svelte" | ||||
|   import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"; | ||||
|   import Tr from "./Base/Tr.svelte"; | ||||
|   import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"; | ||||
|   import FloatOver from "./Base/FloatOver.svelte"; | ||||
|   import PrivacyPolicy from "./BigComponents/PrivacyPolicy"; | ||||
|   import Constants from "../Models/Constants"; | ||||
|   import TabbedGroup from "./Base/TabbedGroup.svelte"; | ||||
|   import UserRelatedState from "../Logic/State/UserRelatedState"; | ||||
|   import LoginToggle from "./Base/LoginToggle.svelte"; | ||||
|   import LoginButton from "./Base/LoginButton.svelte"; | ||||
|   import CopyrightPanel from "./BigComponents/CopyrightPanel"; | ||||
|   import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"; | ||||
|   import ModalRight from "./Base/ModalRight.svelte"; | ||||
|   import { Utils } from "../Utils"; | ||||
|   import Hotkeys from "./Base/Hotkeys"; | ||||
|   import { VariableUiElement } from "./Base/VariableUIElement"; | ||||
|   import SvelteUIElement from "./Base/SvelteUIElement"; | ||||
|   import OverlayToggle from "./BigComponents/OverlayToggle.svelte"; | ||||
|   import LevelSelector from "./BigComponents/LevelSelector.svelte"; | ||||
|   import ExtraLinkButton from "./BigComponents/ExtraLinkButton"; | ||||
|   import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"; | ||||
|   import Svg from "../Svg"; | ||||
|   import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"; | ||||
|   import type { RasterLayerPolygon } from "../Models/RasterLayers"; | ||||
|   import { AvailableRasterLayers } from "../Models/RasterLayers"; | ||||
|   import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"; | ||||
|   import IfHidden from "./Base/IfHidden.svelte"; | ||||
|   import { onDestroy } from "svelte"; | ||||
|   import { OpenJosm } from "./BigComponents/OpenJosm"; | ||||
|   import MapillaryLink from "./BigComponents/MapillaryLink.svelte"; | ||||
|   import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"; | ||||
|   import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"; | ||||
|   import StateIndicator from "./BigComponents/StateIndicator.svelte"; | ||||
|   import LanguagePicker from "./LanguagePicker"; | ||||
|   import Locale from "./i18n/Locale"; | ||||
|   import ShareScreen from "./BigComponents/ShareScreen.svelte"; | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let layout = state.layout | ||||
|   export let state: ThemeViewState; | ||||
|   let layout = state.layout; | ||||
| 
 | ||||
|   let maplibremap: UIEventSource<MlMap> = state.map | ||||
|   let selectedElement: UIEventSource<Feature> = state.selectedElement | ||||
|   let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer | ||||
|   let maplibremap: UIEventSource<MlMap> = state.map; | ||||
|   let selectedElement: UIEventSource<Feature> = state.selectedElement; | ||||
|   let selectedLayer: UIEventSource<LayerConfig> = state.selectedLayer; | ||||
| 
 | ||||
|   const selectedElementView = selectedElement.map( | ||||
|     (selectedElement) => { | ||||
|       // Svelte doesn't properly reload some of the legacy UI-elements | ||||
|       // 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 | ||||
|       const layer = selectedLayer.data | ||||
|       const layer = selectedLayer.data; | ||||
|       if (selectedElement === undefined || layer === undefined) { | ||||
|         return undefined | ||||
|         return undefined; | ||||
|       } | ||||
| 
 | ||||
|       if (!(layer.tagRenderings?.length > 0) || layer.title === undefined) { | ||||
|         return undefined | ||||
|         return undefined; | ||||
|       } | ||||
| 
 | ||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id) | ||||
|       return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }) | ||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id); | ||||
|       return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }); | ||||
|     }, | ||||
|     [selectedLayer] | ||||
|   ) | ||||
|   ); | ||||
| 
 | ||||
|   const selectedElementTitle = selectedElement.map( | ||||
|     (selectedElement) => { | ||||
|       // Svelte doesn't properly reload some of the legacy UI-elements | ||||
|       // 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 | ||||
|       const layer = selectedLayer.data | ||||
|       const layer = selectedLayer.data; | ||||
|       if (selectedElement === undefined || layer === undefined) { | ||||
|         return undefined | ||||
|         return undefined; | ||||
|       } | ||||
| 
 | ||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id) | ||||
|       return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags }) | ||||
|       const tags = state.featureProperties.getStore(selectedElement.properties.id); | ||||
|       return new SvelteUIElement(SelectedElementTitle, { state, layer, selectedElement, tags }); | ||||
|     }, | ||||
|     [selectedLayer] | ||||
|   ) | ||||
|   ); | ||||
| 
 | ||||
|   let mapproperties: MapProperties = state.mapProperties | ||||
|   let featureSwitches: FeatureSwitchState = state.featureSwitches | ||||
|   let availableLayers = state.availableLayers | ||||
|   let userdetails = state.osmConnection.userDetails | ||||
|   let currentViewLayer = layout.layers.find((l) => l.id === "current_view") | ||||
|   let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer | ||||
|   let mapproperties: MapProperties = state.mapProperties; | ||||
|   let featureSwitches: FeatureSwitchState = state.featureSwitches; | ||||
|   let availableLayers = state.availableLayers; | ||||
|   let userdetails = state.osmConnection.userDetails; | ||||
|   let currentViewLayer = layout.layers.find((l) => l.id === "current_view"); | ||||
|   let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer; | ||||
|   let rasterLayerName = | ||||
|     rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name | ||||
|     rasterLayer.data?.properties?.name ?? AvailableRasterLayers.maptilerDefaultLayer.properties.name; | ||||
|   onDestroy( | ||||
|     rasterLayer.addCallbackAndRunD((l) => { | ||||
|       rasterLayerName = l.properties.name | ||||
|       rasterLayerName = l.properties.name; | ||||
|     }) | ||||
|   ) | ||||
|   ); | ||||
| </script> | ||||
| 
 | ||||
| <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> | ||||
|  | @ -169,22 +168,30 @@ | |||
| <div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen"> | ||||
|   <!-- bottom controls --> | ||||
|   <div class="flex w-full items-end justify-between px-4"> | ||||
|     <div class="flex"> | ||||
|       <!-- bottom left elements --> | ||||
|       <MapControlButton on:click={() => state.guistate.openFilterView()}> | ||||
|         <ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")}/> | ||||
|       </MapControlButton> | ||||
|     <div class="flex flex-col"> | ||||
|       <span> | ||||
|       <button class="w-fit pointer-events-auto" on:click={() => {state.openNewDialog()}}> | ||||
|         <Tr t={Translations.t.general.add.title}/> | ||||
|       </button> | ||||
|       </span> | ||||
|        | ||||
|       <OpenBackgroundSelectorButton hideTooltip={true} {state} /> | ||||
|       <a | ||||
|         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={() => { | ||||
|       <div class="flex"> | ||||
|         <!-- bottom left elements --> | ||||
|         <MapControlButton on:click={() => state.guistate.openFilterView()}> | ||||
|           <ToSvelte construct={Svg.filter_svg().SetClass("h-6 w-6")} /> | ||||
|         </MapControlButton> | ||||
| 
 | ||||
|         <OpenBackgroundSelectorButton hideTooltip={true} {state} /> | ||||
|         <a | ||||
|           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.themeIsOpened.setData(true) | ||||
|         }} | ||||
|       > | ||||
|         © OpenStreetMap, <span class="w-24">{rasterLayerName}</span> | ||||
|       </a> | ||||
|         > | ||||
|           © OpenStreetMap, <span class="w-24">{rasterLayerName}</span> | ||||
|         </a> | ||||
|       </div> | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="flex flex-col items-end"> | ||||
|  | @ -319,7 +326,7 @@ | |||
| 
 | ||||
|       <ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" /> | ||||
| 
 | ||||
|       <div slot="title4" class="flex"> | ||||
|       <div class="flex" slot="title4"> | ||||
|         <ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} /> | ||||
|         <Tr t={Translations.t.general.sharescreen.title} /> | ||||
|       </div> | ||||
|  | @ -347,7 +354,9 @@ | |||
| 
 | ||||
| <If condition={state.guistate.menuIsOpened}> | ||||
|   <!-- Menu page --> | ||||
|   <FloatOver> | ||||
|   <FloatOver on:close={() => { | ||||
|       selectedElement.setData(undefined) | ||||
|     }}> | ||||
|     <span slot="close-button"><!-- Hide the default close button --></span> | ||||
|     <TabbedGroup tab={state.guistate.menuViewTabIndex}> | ||||
|       <div slot="post-tablist"> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue