mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
feat(web+server): map improvements (#2498)
* feat(web+server): map improvements * add number format double to fix mobile
This commit is contained in:
50
web/src/api/open-api/api.ts
generated
50
web/src/api/open-api/api.ts
generated
@@ -1471,10 +1471,10 @@ export interface LogoutResponseDto {
|
||||
export interface MapMarkerResponseDto {
|
||||
/**
|
||||
*
|
||||
* @type {AssetTypeEnum}
|
||||
* @type {string}
|
||||
* @memberof MapMarkerResponseDto
|
||||
*/
|
||||
'type': AssetTypeEnum;
|
||||
'id': string;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
@@ -1487,15 +1487,7 @@ export interface MapMarkerResponseDto {
|
||||
* @memberof MapMarkerResponseDto
|
||||
*/
|
||||
'lon': number;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof MapMarkerResponseDto
|
||||
*/
|
||||
'id': string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @export
|
||||
@@ -4858,14 +4850,12 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Get all assets that have GPS information embedded
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {number} [skip]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getMapMarkers: async (isFavorite?: boolean, isArchived?: boolean, skip?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
getMapMarkers: async (isFavorite?: boolean, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/asset/map-marker`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
@@ -4891,14 +4881,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||
localVarQueryParameter['isFavorite'] = isFavorite;
|
||||
}
|
||||
|
||||
if (isArchived !== undefined) {
|
||||
localVarQueryParameter['isArchived'] = isArchived;
|
||||
}
|
||||
|
||||
if (skip !== undefined) {
|
||||
localVarQueryParameter['skip'] = skip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
@@ -5471,15 +5453,13 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
* Get all assets that have GPS information embedded
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {number} [skip]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async getMapMarkers(isFavorite?: boolean, isArchived?: boolean, skip?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MapMarkerResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMapMarkers(isFavorite, isArchived, skip, options);
|
||||
async getMapMarkers(isFavorite?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<MapMarkerResponseDto>>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getMapMarkers(isFavorite, options);
|
||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||
},
|
||||
/**
|
||||
@@ -5739,15 +5719,13 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||
return localVarFp.getCuratedObjects(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Get all assets that have GPS information embedded
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {number} [skip]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
getMapMarkers(isFavorite?: boolean, isArchived?: boolean, skip?: number, options?: any): AxiosPromise<Array<MapMarkerResponseDto>> {
|
||||
return localVarFp.getMapMarkers(isFavorite, isArchived, skip, options).then((request) => request(axios, basePath));
|
||||
getMapMarkers(isFavorite?: boolean, options?: any): AxiosPromise<Array<MapMarkerResponseDto>> {
|
||||
return localVarFp.getMapMarkers(isFavorite, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Get all asset of a device that are in the database, ID only.
|
||||
@@ -6036,16 +6014,14 @@ export class AssetApi extends BaseAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all assets that have GPS information embedded
|
||||
*
|
||||
* @param {boolean} [isFavorite]
|
||||
* @param {boolean} [isArchived]
|
||||
* @param {number} [skip]
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
* @memberof AssetApi
|
||||
*/
|
||||
public getMapMarkers(isFavorite?: boolean, isArchived?: boolean, skip?: number, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getMapMarkers(isFavorite, isArchived, skip, options).then((request) => request(this.axios, this.basePath));
|
||||
public getMapMarkers(isFavorite?: boolean, options?: AxiosRequestConfig) {
|
||||
return AssetApiFp(this.configuration).getMapMarkers(isFavorite, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
{#if latlng}
|
||||
<div class="h-[360px]">
|
||||
{#await import('../shared-components/leaflet') then { Map, TileLayer, Marker }}
|
||||
<Map {latlng} zoom={14}>
|
||||
<Map center={latlng} zoom={14}>
|
||||
<TileLayer
|
||||
urlTemplate={'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'}
|
||||
options={{
|
||||
|
||||
40
web/src/lib/components/map-page/map-settings-modal.svelte
Normal file
40
web/src/lib/components/map-page/map-settings-modal.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts" context="module">
|
||||
export interface MapSettings {
|
||||
allowDarkMode: boolean;
|
||||
onlyFavorites: boolean;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import SettingSwitch from '../admin-page/settings/setting-switch.svelte';
|
||||
import Button from '../elements/buttons/button.svelte';
|
||||
|
||||
export let settings: MapSettings;
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
close: void;
|
||||
save: MapSettings;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
|
||||
<div
|
||||
class="flex flex-col gap-8 border bg-white dark:bg-immich-dark-gray dark:border-immich-dark-gray p-8 shadow-sm w-96 max-w-lg rounded-3xl"
|
||||
>
|
||||
<h1 class="text-2xl text-immich-primary dark:text-immich-dark-primary font-medium self-center">
|
||||
Map Settings
|
||||
</h1>
|
||||
|
||||
<form on:submit|preventDefault={() => dispatch('save', settings)} class="flex flex-col gap-4">
|
||||
<SettingSwitch title="Allow dark mode" bind:checked={settings.allowDarkMode} />
|
||||
<SettingSwitch title="Show only favorites" bind:checked={settings.onlyFavorites} />
|
||||
|
||||
<div class="flex w-full gap-4 mt-4">
|
||||
<Button color="gray" size="sm" fullwidth on:click={() => dispatch('close')}>Cancel</Button>
|
||||
<Button type="submit" size="sm" fullwidth>Save</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</FullScreenModal>
|
||||
@@ -3,7 +3,7 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const dispatch = createEventDispatcher<{ clickOutside: void }>();
|
||||
</script>
|
||||
|
||||
<section
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
.marker-cluster {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.asset-marker-icon {
|
||||
@apply rounded-full;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgb(69, 80, 169);
|
||||
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px,
|
||||
rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px,
|
||||
rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
|
||||
}
|
||||
|
||||
.marker-cluster div {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
text-align: center;
|
||||
@apply rounded-full;
|
||||
font-weight: bold;
|
||||
|
||||
background-color: rgb(236, 237, 246);
|
||||
border: 1px solid rgb(69, 80, 169);
|
||||
|
||||
color: rgb(69, 80, 169);
|
||||
box-shadow: rgba(5, 5, 122, 0.12) 0px 2px 4px 0px, rgba(4, 4, 230, 0.32) 0px 2px 16px 0px;
|
||||
}
|
||||
|
||||
.dark .marker-cluster div {
|
||||
background-color: #adcbfa;
|
||||
border: 1px solid black;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.marker-cluster span {
|
||||
line-height: 40px;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
import { createContext } from '$lib/utils/context';
|
||||
import { MarkerClusterGroup, Marker, Icon, LeafletEvent } from 'leaflet';
|
||||
import { Icon, LeafletEvent, Marker, MarkerClusterGroup } from 'leaflet';
|
||||
|
||||
const { get: getContext, set: setClusterContext } = createContext<() => MarkerClusterGroup>();
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import 'leaflet.markercluster';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { getMapContext } from './map.svelte';
|
||||
import { MapMarkerResponseDto, api } from '@api';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import 'leaflet.markercluster';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import './asset-marker-cluster.css';
|
||||
import { getMapContext } from './map.svelte';
|
||||
|
||||
class AssetMarker extends Marker {
|
||||
marker: MapMarkerResponseDto;
|
||||
@@ -95,49 +95,3 @@
|
||||
if (cluster) cluster.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if cluster}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
:global(.marker-cluster) {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
:global(.asset-marker-icon) {
|
||||
@apply rounded-full;
|
||||
object-fit: cover;
|
||||
border: 1px solid rgb(69, 80, 169);
|
||||
box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px,
|
||||
rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px,
|
||||
rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
|
||||
}
|
||||
|
||||
:global(.marker-cluster div) {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 5px;
|
||||
margin-top: 5px;
|
||||
|
||||
text-align: center;
|
||||
@apply rounded-full;
|
||||
font-weight: bold;
|
||||
|
||||
background-color: rgb(236, 237, 246);
|
||||
border: 1px solid rgb(69, 80, 169);
|
||||
|
||||
color: rgb(69, 80, 169);
|
||||
box-shadow: rgba(5, 5, 122, 0.12) 0px 2px 4px 0px, rgba(4, 4, 230, 0.32) 0px 2px 16px 0px;
|
||||
}
|
||||
|
||||
:global(.dark .marker-cluster div) {
|
||||
background-color: #adcbfa;
|
||||
border: 1px solid black;
|
||||
color: black;
|
||||
}
|
||||
|
||||
:global(.marker-cluster span) {
|
||||
line-height: 40px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { Control, type ControlPosition } from 'leaflet';
|
||||
import { getMapContext } from './map.svelte';
|
||||
|
||||
export let position: ControlPosition | undefined = undefined;
|
||||
let className: string | undefined = undefined;
|
||||
export { className as class };
|
||||
|
||||
let control: Control;
|
||||
let target: HTMLDivElement;
|
||||
|
||||
const map = getMapContext();
|
||||
|
||||
onMount(() => {
|
||||
const ControlClass = Control.extend({
|
||||
position,
|
||||
onAdd: () => target
|
||||
});
|
||||
|
||||
control = new ControlClass().addTo(map);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
control.remove();
|
||||
});
|
||||
|
||||
$: if (control && position) {
|
||||
control.setPosition(position);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={target} class={className}>
|
||||
<slot />
|
||||
</div>
|
||||
@@ -1,4 +1,5 @@
|
||||
export { default as AssetMarkerCluster } from './asset-marker-cluster.svelte';
|
||||
export { default as Control } from './control.svelte';
|
||||
export { default as Map } from './map.svelte';
|
||||
export { default as Marker } from './marker.svelte';
|
||||
export { default as TileLayer } from './tile-layer.svelte';
|
||||
export { default as AssetMarkerCluster } from './asset-marker-cluster.svelte';
|
||||
|
||||
@@ -12,11 +12,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { Map, type LatLngExpression } from 'leaflet';
|
||||
import { Map, type LatLngExpression, type MapOptions } from 'leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
|
||||
export let latlng: LatLngExpression;
|
||||
export let center: LatLngExpression;
|
||||
export let zoom: number;
|
||||
export let options: MapOptions | undefined = undefined;
|
||||
export let allowDarkMode = false;
|
||||
let container: HTMLDivElement;
|
||||
let map: Map;
|
||||
|
||||
@@ -24,7 +26,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
map = new Map(container);
|
||||
map = new Map(container, options);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -32,11 +34,17 @@
|
||||
if (map) map.remove();
|
||||
});
|
||||
|
||||
$: if (map) map.setView(latlng, zoom);
|
||||
$: if (map) map.setView(center, zoom);
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="w-full h-full">
|
||||
<div bind:this={container} class="w-full h-full" class:map-dark={allowDarkMode}>
|
||||
{#if map}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.dark) .map-dark :global(.leaflet-layer) {
|
||||
filter: invert(100%) brightness(130%) saturate(0%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,30 +5,16 @@
|
||||
|
||||
export let urlTemplate: string;
|
||||
export let options: TileLayerOptions | undefined = undefined;
|
||||
export let allowDarkMode = false;
|
||||
|
||||
let tileLayer: TileLayer;
|
||||
|
||||
const map = getMapContext();
|
||||
|
||||
onMount(() => {
|
||||
tileLayer = new TileLayer(urlTemplate, {
|
||||
className: allowDarkMode ? 'leaflet-layer-dynamic' : 'leaflet-layer',
|
||||
...options
|
||||
}).addTo(map);
|
||||
tileLayer = new TileLayer(urlTemplate, options).addTo(map);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (tileLayer) tileLayer.remove();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:global(.leaflet-layer-dynamic) {
|
||||
filter: brightness(100%) contrast(100%) saturate(80%);
|
||||
}
|
||||
|
||||
:global(.dark .leaflet-layer-dynamic) {
|
||||
filter: invert(100%) brightness(130%) saturate(0%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { MapSettings } from '$lib/components/map-page/map-settings-modal.svelte';
|
||||
import { persisted } from 'svelte-local-storage-store';
|
||||
|
||||
const initialTheme =
|
||||
@@ -19,3 +20,8 @@ export const locale = persisted<string | undefined>('locale', undefined, {
|
||||
stringify: (obj) => obj ?? ''
|
||||
}
|
||||
});
|
||||
|
||||
export const mapSettings = persisted<MapSettings>('map-settings', {
|
||||
allowDarkMode: true,
|
||||
onlyFavorites: false
|
||||
});
|
||||
|
||||
@@ -2,22 +2,15 @@ import { AppRoute } from '$lib/constants';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load = (async ({ locals: { api, user } }) => {
|
||||
export const load = (async ({ locals: { user } }) => {
|
||||
if (!user) {
|
||||
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||
}
|
||||
|
||||
try {
|
||||
const { data: mapMarkers } = await api.assetApi.getMapMarkers();
|
||||
|
||||
return {
|
||||
user,
|
||||
mapMarkers,
|
||||
meta: {
|
||||
title: 'Map'
|
||||
}
|
||||
};
|
||||
} catch (e) {
|
||||
throw redirect(302, AppRoute.AUTH_LOGIN);
|
||||
}
|
||||
return {
|
||||
user,
|
||||
meta: {
|
||||
title: 'Map'
|
||||
}
|
||||
};
|
||||
}) satisfies PageServerLoad;
|
||||
|
||||
@@ -1,27 +1,43 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from '../map/$types';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
|
||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||
import {
|
||||
assetInteractionStore,
|
||||
isViewingAssetStoreState,
|
||||
viewingAssetStoreState
|
||||
} from '$lib/stores/asset-interaction.store';
|
||||
import { mapSettings } from '$lib/stores/preferences.store';
|
||||
import { MapMarkerResponseDto, api } from '@api';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import Cog from 'svelte-material-icons/Cog.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let initialMapCenter: [number, number] = [48, 11];
|
||||
|
||||
$: {
|
||||
if (data.mapMarkers.length) {
|
||||
let firstMarker = data.mapMarkers[0];
|
||||
initialMapCenter = [firstMarker.lat, firstMarker.lon];
|
||||
}
|
||||
}
|
||||
|
||||
let mapMarkersPromise: Promise<MapMarkerResponseDto[]>;
|
||||
let abortController = new AbortController();
|
||||
let viewingAssets: string[] = [];
|
||||
let viewingAssetCursor = 0;
|
||||
let showSettingsModal = false;
|
||||
|
||||
onMount(() => {
|
||||
mapMarkersPromise = loadMapMarkers();
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
abortController.abort();
|
||||
assetInteractionStore.clearMultiselect();
|
||||
assetInteractionStore.setIsViewingAsset(false);
|
||||
});
|
||||
|
||||
async function loadMapMarkers() {
|
||||
const { data } = await api.assetApi.getMapMarkers($mapSettings.onlyFavorites || undefined, {
|
||||
signal: abortController.signal
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function onViewAssets(assets: string[]) {
|
||||
assetInteractionStore.setViewingAssetId(assets[0]);
|
||||
@@ -40,27 +56,55 @@
|
||||
assetInteractionStore.setViewingAssetId(viewingAssets[--viewingAssetCursor]);
|
||||
}
|
||||
}
|
||||
|
||||
function getMapCenter(mapMarkers: MapMarkerResponseDto[]): [number, number] {
|
||||
const marker = mapMarkers[0];
|
||||
if (marker) {
|
||||
return [marker.lat, marker.lon];
|
||||
}
|
||||
|
||||
return [48, 11];
|
||||
}
|
||||
</script>
|
||||
|
||||
<UserPageLayout user={data.user} title={data.meta.title}>
|
||||
<div slot="buttons" />
|
||||
|
||||
<div class="h-full w-full relative z-0">
|
||||
{#await import('$lib/components/shared-components/leaflet') then { Map, TileLayer, AssetMarkerCluster }}
|
||||
<Map latlng={initialMapCenter} zoom={7}>
|
||||
<TileLayer
|
||||
allowDarkMode={true}
|
||||
urlTemplate={'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'}
|
||||
<div class="h-full w-full isolate">
|
||||
{#await import('$lib/components/shared-components/leaflet') then { Map, TileLayer, AssetMarkerCluster, Control }}
|
||||
{#await mapMarkersPromise then mapMarkers}
|
||||
<Map
|
||||
center={getMapCenter(mapMarkers)}
|
||||
zoom={7}
|
||||
allowDarkMode={$mapSettings.allowDarkMode}
|
||||
options={{
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
maxBounds: [
|
||||
[-90, -180],
|
||||
[90, 180]
|
||||
],
|
||||
minZoom: 3
|
||||
}}
|
||||
/>
|
||||
<AssetMarkerCluster
|
||||
markers={data.mapMarkers}
|
||||
on:view={(event) => onViewAssets(event.detail.assets)}
|
||||
/>
|
||||
</Map>
|
||||
>
|
||||
<TileLayer
|
||||
urlTemplate={'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'}
|
||||
options={{
|
||||
attribution:
|
||||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||
}}
|
||||
/>
|
||||
<AssetMarkerCluster
|
||||
markers={mapMarkers}
|
||||
on:view={(event) => onViewAssets(event.detail.assets)}
|
||||
/>
|
||||
<Control>
|
||||
<button
|
||||
class="flex justify-center items-center bg-white text-black/70 w-8 h-8 font-bold rounded-sm border-2 border-black/20 hover:bg-gray-50 focus:bg-gray-50"
|
||||
title="Open map settings"
|
||||
on:click={() => (showSettingsModal = true)}
|
||||
>
|
||||
<Cog size="100%" class="p-1" />
|
||||
</button>
|
||||
</Control>
|
||||
</Map>
|
||||
{/await}
|
||||
{/await}
|
||||
</div>
|
||||
</UserPageLayout>
|
||||
@@ -78,3 +122,20 @@
|
||||
/>
|
||||
{/if}
|
||||
</Portal>
|
||||
|
||||
{#if showSettingsModal}
|
||||
<MapSettingsModal
|
||||
settings={{ ...$mapSettings }}
|
||||
on:close={() => (showSettingsModal = false)}
|
||||
on:save={async ({ detail }) => {
|
||||
const shouldUpdate = detail.onlyFavorites !== $mapSettings.onlyFavorites;
|
||||
showSettingsModal = false;
|
||||
$mapSettings = detail;
|
||||
|
||||
if (shouldUpdate) {
|
||||
const markers = await loadMapMarkers();
|
||||
mapMarkersPromise = Promise.resolve(markers);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user