mirror of https://github.com/immich-app/immich.git
feat(web+server): map improvements (#2498)
* feat(web+server): map improvements * add number format double to fix mobilepull/2506/head
parent
e028cf9002
commit
a7b9adc692
@ -0,0 +1,20 @@
|
||||
import { AssetService, AuthUserDto, MapMarkerResponseDto } from '@app/domain';
|
||||
import { MapMarkerDto } from '@app/domain/asset/dto/map-marker.dto';
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { GetAuthUser } from '../decorators/auth-user.decorator';
|
||||
import { Authenticated } from '../decorators/authenticated.decorator';
|
||||
import { UseValidation } from '../decorators/use-validation.decorator';
|
||||
|
||||
@ApiTags('Asset')
|
||||
@Controller('asset')
|
||||
@Authenticated()
|
||||
@UseValidation()
|
||||
export class AssetController {
|
||||
constructor(private service: AssetService) {}
|
||||
|
||||
@Get('/map-marker')
|
||||
getMapMarkers(@GetAuthUser() authUser: AuthUserDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||
return this.service.getMapMarkers(authUser, options);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
import { toBoolean } from 'apps/immich/src/utils/transform.util';
|
||||
import { Transform } from 'class-transformer';
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
|
||||
export class MapMarkerDto {
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@Transform(toBoolean)
|
||||
isFavorite?: boolean;
|
||||
}
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue