2023-03-24 19:21:15 +01:00
|
|
|
<script lang="ts">
|
2023-05-05 01:25:37 +02:00
|
|
|
import {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 RasterLayerPicker from "./Map/RasterLayerPicker.svelte";
|
|
|
|
import ThemeViewState from "../Models/ThemeViewState";
|
|
|
|
import type {MapProperties} from "../Models/MapProperties";
|
|
|
|
import Geosearch from "./BigComponents/Geosearch.svelte";
|
|
|
|
import Translations from "./i18n/Translations";
|
2023-05-07 23:19:30 +02:00
|
|
|
import {CogIcon, EyeIcon, MenuIcon, XCircleIcon} from "@rgossiaux/svelte-heroicons/solid";
|
2023-05-05 01:25:37 +02:00
|
|
|
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 "./BigComponents/DownloadPanel";
|
|
|
|
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 Svg from "../Svg";
|
2023-05-05 02:03:41 +02:00
|
|
|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
|
2023-05-07 02:26:30 +02:00
|
|
|
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
|
2023-05-05 01:25:37 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-05-07 02:26:30 +02:00
|
|
|
const selectedElementView = selectedElement.map(selectedElement => {
|
2023-05-05 01:25:37 +02:00
|
|
|
// 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;
|
|
|
|
if (selectedElement === undefined || layer === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
2023-05-07 02:26:30 +02:00
|
|
|
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;
|
|
|
|
if (selectedElement === undefined || layer === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tags = state.featureProperties.getStore(selectedElement.properties.id);
|
|
|
|
return new SvelteUIElement(SelectedElementTitle, {state, layer, selectedElement, tags})
|
2023-05-05 01:25:37 +02:00
|
|
|
}, [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")
|
2023-03-24 19:21:15 +01:00
|
|
|
</script>
|
|
|
|
|
|
|
|
|
2023-04-24 02:27:55 +02:00
|
|
|
<div class="h-screen w-screen absolute top-0 left-0 overflow-hidden">
|
2023-05-05 01:25:37 +02:00
|
|
|
<MaplibreMap map={maplibremap}></MaplibreMap>
|
2023-03-24 19:21:15 +01:00
|
|
|
</div>
|
|
|
|
|
2023-04-24 02:27:55 +02:00
|
|
|
<div class="absolute top-0 left-0 w-full ">
|
2023-05-05 01:25:37 +02:00
|
|
|
<!-- Top components -->
|
|
|
|
<If condition={state.featureSwitches.featureSwitchSearch}>
|
|
|
|
<div class="max-[480px]:w-full float-right mt-1 px-1 sm:m-2">
|
|
|
|
<Geosearch bounds={state.mapProperties.bounds} perLayer={state.perLayer} {selectedElement}
|
|
|
|
{selectedLayer}></Geosearch>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<div class="float-left m-1 sm:mt-2">
|
|
|
|
<MapControlButton on:click={() => state.guistate.themeIsOpened.setData(true)}>
|
|
|
|
<div class="flex m-0.5 mx-1 sm:mx-1 md:mx-2 items-center cursor-pointer max-[480px]:w-full">
|
|
|
|
<img class="w-6 h-6 md:w-8 md:h-8 block mr-0.5 sm:mr-1 md:mr-2" src={layout.icon}>
|
|
|
|
<b class="mr-1">
|
|
|
|
<Tr t={layout.title}></Tr>
|
|
|
|
</b>
|
|
|
|
</div>
|
|
|
|
</MapControlButton>
|
|
|
|
<MapControlButton on:click={() =>state.guistate.menuIsOpened.setData(true)}>
|
|
|
|
<MenuIcon class="w-8 h-8 cursor-pointer"></MenuIcon>
|
|
|
|
</MapControlButton>
|
|
|
|
{#if currentViewLayer?.tagRenderings}
|
|
|
|
<MapControlButton
|
|
|
|
on:click={() => {selectedLayer.setData(currentViewLayer); selectedElement.setData(state.currentView.features?.data?.[0])}}>
|
|
|
|
<ToSvelte
|
|
|
|
construct={() =>(currentViewLayer.defaultIcon() ?? Svg.checkbox_empty_svg()).SetClass("w-8 h-8 cursor-pointer")}/>
|
|
|
|
</MapControlButton>
|
|
|
|
{/if}
|
2023-05-05 02:03:41 +02:00
|
|
|
<ToSvelte construct={() => new ExtraLinkButton(state, layout.extraLink)}></ToSvelte>
|
2023-05-05 01:25:37 +02:00
|
|
|
<If condition={state.featureSwitchIsTesting}>
|
2023-05-05 02:03:41 +02:00
|
|
|
<span class="alert">
|
|
|
|
Testmode
|
|
|
|
</span>
|
2023-05-05 01:25:37 +02:00
|
|
|
</If>
|
2023-04-24 02:27:55 +02:00
|
|
|
|
2023-05-05 01:25:37 +02:00
|
|
|
</div>
|
2023-03-24 19:21:15 +01:00
|
|
|
</div>
|
|
|
|
|
2023-03-28 05:13:48 +02:00
|
|
|
<div class="absolute bottom-0 left-0 mb-4 ml-4">
|
2023-03-29 17:21:20 +02:00
|
|
|
|
2023-03-24 19:21:15 +01:00
|
|
|
</div>
|
|
|
|
|
2023-04-26 18:04:42 +02:00
|
|
|
<div class="absolute bottom-0 right-0 mb-4 mr-4 flex flex-col items-end">
|
2023-05-05 01:25:37 +02:00
|
|
|
<If condition={state.floors.map(f => f.length > 1)}>
|
|
|
|
<div class="mr-0.5">
|
|
|
|
<LevelSelector floors={state.floors} layerState={state.layerState} zoom={state.mapProperties.zoom}/>
|
|
|
|
</div>
|
|
|
|
</If>
|
|
|
|
<MapControlButton on:click={() => mapproperties.zoom.update(z => z+1)}>
|
|
|
|
<img class="w-8 h-8" src="./assets/svg/plus.svg"/>
|
|
|
|
</MapControlButton>
|
|
|
|
<MapControlButton on:click={() => mapproperties.zoom.update(z => z-1)}>
|
|
|
|
<img class="w-8 h-8" src="./assets/svg/min.svg"/>
|
2023-03-24 19:21:15 +01:00
|
|
|
</MapControlButton>
|
2023-05-05 01:25:37 +02:00
|
|
|
<If condition={featureSwitches.featureSwitchGeolocation}>
|
|
|
|
<MapControlButton>
|
|
|
|
<ToSvelte
|
|
|
|
construct={new GeolocationControl(state.geolocation, mapproperties).SetClass("block w-8 h-8")}></ToSvelte>
|
|
|
|
</MapControlButton>
|
|
|
|
</If>
|
2023-03-24 19:21:15 +01:00
|
|
|
</div>
|
|
|
|
|
2023-05-07 02:26:30 +02:00
|
|
|
<If condition={selectedElementView.map(v => v !== undefined && selectedLayer.data !== undefined && !selectedLayer.data.popupInFloatover,[ selectedLayer] )}>
|
2023-05-05 01:25:37 +02:00
|
|
|
<ModalRight on:close={() => {selectedElement.setData(undefined)}}>
|
2023-05-07 02:26:30 +02:00
|
|
|
<div class="absolute flex flex-col h-full w-full normal-background">
|
2023-05-07 23:19:30 +02:00
|
|
|
<ToSvelte construct={new VariableUiElement(selectedElementTitle)}>
|
|
|
|
<!-- Title -->
|
|
|
|
</ToSvelte>
|
|
|
|
<ToSvelte construct={new VariableUiElement(selectedElementView).SetClass("overflow-auto")}>
|
|
|
|
<!-- Main view -->
|
|
|
|
</ToSvelte>
|
2023-05-07 02:26:30 +02:00
|
|
|
</div>
|
2023-05-05 01:25:37 +02:00
|
|
|
</ModalRight>
|
2023-04-21 00:25:56 +02:00
|
|
|
</If>
|
2023-03-28 05:13:48 +02:00
|
|
|
|
2023-05-07 02:26:30 +02:00
|
|
|
<If condition={selectedElementView.map(v => v !== undefined && selectedLayer.data !== undefined && selectedLayer.data.popupInFloatover,[ selectedLayer] )}>
|
2023-05-05 01:25:37 +02:00
|
|
|
<FloatOver on:close={() => {selectedElement.setData(undefined)}}>
|
2023-05-07 02:26:30 +02:00
|
|
|
<ToSvelte construct={new VariableUiElement(selectedElementView)}></ToSvelte>
|
2023-05-05 01:25:37 +02:00
|
|
|
</FloatOver>
|
2023-05-05 01:00:15 +02:00
|
|
|
</If>
|
|
|
|
|
2023-04-06 01:33:08 +02:00
|
|
|
<If condition={state.guistate.themeIsOpened}>
|
2023-05-07 23:19:30 +02:00
|
|
|
<!-- Theme menu -->
|
|
|
|
<FloatOver>
|
|
|
|
<span slot="close-button"><!-- Disable the close button --></span>
|
2023-05-05 01:25:37 +02:00
|
|
|
<TabbedGroup tab={state.guistate.themeViewTabIndex}>
|
2023-05-07 23:19:30 +02:00
|
|
|
<div slot="post-tablist">
|
|
|
|
<XCircleIcon class="w-8 h-8 mr-2" on:click={() => state.guistate.themeIsOpened.setData(false)}/>
|
|
|
|
</div>
|
|
|
|
|
2023-05-05 01:25:37 +02:00
|
|
|
<Tr slot="title0" t={layout.title}/>
|
|
|
|
|
|
|
|
<div slot="content0">
|
|
|
|
|
|
|
|
<Tr t={layout.description}></Tr>
|
|
|
|
<Tr t={Translations.t.general.welcomeExplanation.general}/>
|
|
|
|
{#if layout.layers.some((l) => l.presets?.length > 0)}
|
|
|
|
<If condition={state.featureSwitches.featureSwitchAddNew}>
|
|
|
|
<Tr t={Translations.t.general.welcomeExplanation.addNew}/>
|
|
|
|
</If>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<!--toTheMap,
|
|
|
|
loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
|
|
|
|
-->
|
|
|
|
<Tr t={layout.descriptionTail}></Tr>
|
|
|
|
<div class="m-x-8">
|
|
|
|
<button class="subtle-background rounded w-full p-4"
|
|
|
|
on:click={() => state.guistate.themeIsOpened.setData(false)}>
|
|
|
|
<Tr t={Translations.t.general.openTheMap}/>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex" slot="title1">
|
|
|
|
<If condition={state.featureSwitches.featureSwitchFilter}>
|
|
|
|
<img class="w-4 h-4" src="./assets/svg/filter.svg">
|
|
|
|
<Tr t={Translations.t.general.menu.filter}/>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex flex-col" slot="content1">
|
|
|
|
{#each layout.layers as layer}
|
|
|
|
<Filterview zoomlevel={state.mapProperties.zoom}
|
|
|
|
filteredLayer={state.layerState.filteredLayers.get(layer.id)}
|
|
|
|
highlightedLayer={state.guistate.highlightedLayerInFilters}></Filterview>
|
|
|
|
{/each}
|
|
|
|
{#each layout.tileLayerSources as tilesource}
|
|
|
|
<OverlayToggle
|
|
|
|
layerproperties={tilesource}
|
|
|
|
state={state.overlayLayerStates.get(tilesource.id)}
|
|
|
|
highlightedLayer={state.guistate.highlightedLayerInFilters}
|
|
|
|
zoomlevel={state.mapProperties.zoom}
|
|
|
|
/>
|
|
|
|
{/each}
|
|
|
|
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
|
|
|
<RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
<div class="flex" slot="title2">
|
|
|
|
<If condition={state.featureSwitches.featureSwitchEnableExport}>
|
|
|
|
<img class="w-4 h-4" src="./assets/svg/download.svg"/>
|
|
|
|
<Tr t={Translations.t.general.download.title}/>
|
|
|
|
</If>
|
|
|
|
</div>
|
|
|
|
<div slot="content2">
|
|
|
|
<ToSvelte construct={() => new DownloadPanel(state)}/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div slot="title3">
|
|
|
|
<Tr t={Translations.t.general.attribution.title}/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3"></ToSvelte>
|
|
|
|
|
|
|
|
|
|
|
|
</TabbedGroup>
|
|
|
|
</FloatOver>
|
2023-03-28 05:13:48 +02:00
|
|
|
</If>
|
|
|
|
|
|
|
|
|
|
|
|
<If condition={state.guistate.menuIsOpened}>
|
2023-05-05 01:25:37 +02:00
|
|
|
<!-- Menu page -->
|
2023-05-07 23:19:30 +02:00
|
|
|
<FloatOver>
|
|
|
|
<span slot="close-button"><!-- Hide the default close button --></span>
|
2023-05-05 01:25:37 +02:00
|
|
|
<TabbedGroup tab={state.guistate.menuViewTabIndex}>
|
2023-05-07 23:19:30 +02:00
|
|
|
<div slot="post-tablist">
|
|
|
|
<XCircleIcon class="w-8 h-8 mr-2" on:click={() => state.guistate.menuIsOpened.setData(false)}/>
|
|
|
|
</div>
|
2023-05-05 01:25:37 +02:00
|
|
|
<div class="flex" slot="title0">
|
|
|
|
<Tr t={Translations.t.general.menu.aboutMapComplete}/>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex flex-col" slot="content0">
|
|
|
|
|
|
|
|
<Tr t={Translations.t.general.aboutMapComplete.intro}/>
|
|
|
|
|
|
|
|
<a class="flex" href={Utils.HomepageLink()}>
|
|
|
|
<img class="w-6 h-6" src="./assets/svg/add.svg">
|
|
|
|
<Tr t={Translations.t.general.backToIndex}/>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<a class="flex" href="https://github.com/pietervdvn/MapComplete/issues" target="_blank">
|
|
|
|
<img class="w-6 h-6" src="./assets/svg/bug.svg">
|
|
|
|
<Tr t={Translations.t.general.attribution.openIssueTracker}/>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
|
|
|
|
<a class="flex" href="https://en.osm.town/@MapComplete" target="_blank">
|
|
|
|
<img class="w-6 h-6" src="./assets/svg/mastodon.svg">
|
|
|
|
<Tr t={Translations.t.general.attribution.followOnMastodon}/>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<a class="flex" href="https://liberapay.com/pietervdvn/" target="_blank">
|
|
|
|
<img class="w-6 h-6" src="./assets/svg/liberapay.svg">
|
|
|
|
<Tr t={Translations.t.general.attribution.donate}/>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<a class="flex" href={Utils.OsmChaLinkFor(7)} target="_blank">
|
|
|
|
<img class="w-6 h-6" src="./assets/svg/statistics.svg">
|
|
|
|
<Tr t={Translations.t.general.attribution.openOsmcha.Subs({theme: "MapComplete"})}/>
|
|
|
|
</a>
|
|
|
|
{Constants.vNumber}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
2023-05-07 23:19:30 +02:00
|
|
|
<div class="flex" slot="title1">
|
|
|
|
<CogIcon class="w-6 h-6"/>
|
|
|
|
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})}/>
|
|
|
|
</div>
|
2023-05-05 01:25:37 +02:00
|
|
|
|
|
|
|
<div slot="content1">
|
|
|
|
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
|
|
|
<LoginToggle {state}>
|
|
|
|
<div slot="not-logged-in">
|
|
|
|
<Tr class="alert" t={Translations.t.userinfo.notLoggedIn}/>
|
|
|
|
<LoginButton osmConnection={state.osmConnection}></LoginButton>
|
|
|
|
</div>
|
|
|
|
<SelectedElementView
|
|
|
|
highlightedRendering={state.guistate.highlightedUserSetting}
|
|
|
|
layer={UserRelatedState.usersettingsConfig} selectedElement={({
|
2023-04-07 02:13:57 +02:00
|
|
|
type:"Feature",properties: {}, geometry: {type:"Point", coordinates: [0,0]}
|
2023-04-20 17:42:07 +02:00
|
|
|
})}
|
2023-05-05 01:25:37 +02:00
|
|
|
{state}
|
|
|
|
tags={state.userRelatedState.preferencesAsTags}
|
|
|
|
/>
|
|
|
|
</LoginToggle>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex" slot="title2">
|
|
|
|
<img class="w-6" src="./assets/svg/community.svg">
|
|
|
|
Get in touch with others
|
|
|
|
</div>
|
|
|
|
<CommunityIndexView location={state.mapProperties.location} slot="content2"></CommunityIndexView>
|
|
|
|
|
|
|
|
<div class="flex" slot="title3">
|
|
|
|
<EyeIcon class="w-6"/>
|
|
|
|
<Tr t={Translations.t.privacy.title}></Tr>
|
|
|
|
</div>
|
|
|
|
<ToSvelte construct={() => new PrivacyPolicy()} slot="content3"></ToSvelte>
|
2023-05-07 23:19:30 +02:00
|
|
|
|
|
|
|
<Tr slot="title4" t={Translations.t.advanced.title}/>
|
|
|
|
<div class="flex flex-col" slot="content4">
|
|
|
|
<ToSvelte construct={Hotkeys.generateDocumentationDynamic}></ToSvelte>
|
|
|
|
|
|
|
|
</div>
|
2023-05-05 01:25:37 +02:00
|
|
|
</TabbedGroup>
|
|
|
|
</FloatOver>
|
2023-03-28 05:13:48 +02:00
|
|
|
</If>
|
|
|
|
|