mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor: change location (#27201)
This commit is contained in:
@@ -1,24 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||||
import Portal from '$lib/elements/Portal.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
|
import { updateAsset, type AssetResponseDto } from '@immich/sdk';
|
||||||
import { Icon } from '@immich/ui';
|
import { Icon, modalManager } from '@immich/ui';
|
||||||
import { mdiMapMarkerOutline, mdiPencil } from '@mdi/js';
|
import { mdiMapMarkerOutline, mdiPencil } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
asset: AssetResponseDto;
|
asset: AssetResponseDto;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { isOwner, asset = $bindable() }: Props = $props();
|
let { isOwner, asset = $bindable() }: Props = $props();
|
||||||
|
|
||||||
let isShowChangeLocation = $state(false);
|
const onAction = async () => {
|
||||||
|
const point = await modalManager.show(GeolocationPointPickerModal, { asset });
|
||||||
const onClose = async (point?: { lng: number; lat: number }) => {
|
|
||||||
isShowChangeLocation = false;
|
|
||||||
|
|
||||||
if (!point) {
|
if (!point) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -38,7 +34,7 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full text-start justify-between place-items-start gap-4 py-4"
|
class="flex w-full text-start justify-between place-items-start gap-4 py-4"
|
||||||
onclick={() => (isOwner ? (isShowChangeLocation = true) : null)}
|
onclick={isOwner ? onAction : undefined}
|
||||||
title={isOwner ? $t('edit_location') : ''}
|
title={isOwner ? $t('edit_location') : ''}
|
||||||
class:hover:text-primary={isOwner}
|
class:hover:text-primary={isOwner}
|
||||||
>
|
>
|
||||||
@@ -72,12 +68,11 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full text-start justify-between place-items-start gap-4 py-4 rounded-lg hover:text-primary"
|
class="flex w-full text-start justify-between place-items-start gap-4 py-4 rounded-lg hover:text-primary"
|
||||||
onclick={() => (isShowChangeLocation = true)}
|
onclick={onAction}
|
||||||
title={$t('add_location')}
|
title={$t('add_location')}
|
||||||
>
|
>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<div><Icon icon={mdiMapMarkerOutline} size="24" /></div>
|
<div><Icon icon={mdiMapMarkerOutline} size="24" /></div>
|
||||||
|
|
||||||
<p>{$t('add_a_location')}</p>
|
<p>{$t('add_a_location')}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="focus:outline-none p-1">
|
<div class="focus:outline-none p-1">
|
||||||
@@ -85,9 +80,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isShowChangeLocation}
|
|
||||||
<Portal>
|
|
||||||
<ChangeLocation {asset} {onClose} />
|
|
||||||
</Portal>
|
|
||||||
{/if}
|
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
|
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
|
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
|
||||||
import { getAssetControlContext } from '$lib/utils/context';
|
import { getAssetControlContext } from '$lib/utils/context';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { updateAssets } from '@immich/sdk';
|
import { updateAssets } from '@immich/sdk';
|
||||||
|
import { modalManager, toastManager } from '@immich/ui';
|
||||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
menuItem?: boolean;
|
menuItem?: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { menuItem = false }: Props = $props();
|
let { menuItem = false }: Props = $props();
|
||||||
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||||
|
|
||||||
let isShowChangeLocation = $state(false);
|
const onAction = async () => {
|
||||||
|
const point = await modalManager.show(GeolocationPointPickerModal, {});
|
||||||
async function handleConfirm(point?: { lng: number; lat: number }) {
|
|
||||||
isShowChangeLocation = false;
|
|
||||||
|
|
||||||
if (!point) {
|
if (!point) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -29,20 +27,14 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
|
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
|
||||||
|
toastManager.primary();
|
||||||
clearSelect();
|
clearSelect();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, $t('errors.unable_to_update_location'));
|
handleError(error, $t('errors.unable_to_update_location'));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if menuItem}
|
{#if menuItem}
|
||||||
<MenuOption
|
<MenuOption text={$t('change_location')} icon={mdiMapMarkerMultipleOutline} onClick={onAction} />
|
||||||
text={$t('change_location')}
|
|
||||||
icon={mdiMapMarkerMultipleOutline}
|
|
||||||
onClick={() => (isShowChangeLocation = true)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if isShowChangeLocation}
|
|
||||||
<ChangeLocation onClose={handleConfirm} />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cleanClass } from '$lib';
|
import { cleanClass, isDefined } from '$lib';
|
||||||
|
|
||||||
describe('cleanClass', () => {
|
describe('cleanClass', () => {
|
||||||
it('should return a string of class names', () => {
|
it('should return a string of class names', () => {
|
||||||
@@ -13,3 +13,19 @@ describe('cleanClass', () => {
|
|||||||
expect(cleanClass('class1', ['class2', 'class3'])).toBe('class1 class2 class3');
|
expect(cleanClass('class1', ['class2', 'class3'])).toBe('class1 class2 class3');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('isDefined', () => {
|
||||||
|
it('should return false for null', () => {
|
||||||
|
expect(isDefined(null)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for undefined', () => {
|
||||||
|
expect(isDefined(undefined)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true for everything else', () => {
|
||||||
|
for (const value of [0, 1, 2, true, false, {}, 'foo', 'bar', []]) {
|
||||||
|
expect(isDefined(value)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -14,3 +14,5 @@ export const cleanClass = (...classNames: unknown[]) => {
|
|||||||
.join(' '),
|
.join(' '),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isDefined = <T>(value: T): value is NonNullable<T> => value !== null && value !== undefined;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { LatLng } from '$lib/types';
|
||||||
|
|
||||||
|
class GeolocationManager {
|
||||||
|
#lastPoint = $state<LatLng>();
|
||||||
|
|
||||||
|
get lastPoint() {
|
||||||
|
return this.#lastPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelected(point: LatLng) {
|
||||||
|
this.#lastPoint = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const geolocationManager = new GeolocationManager();
|
||||||
+28
-26
@@ -1,30 +1,27 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { isDefined } from '$lib';
|
||||||
import { clickOutside } from '$lib/actions/click-outside';
|
import { clickOutside } from '$lib/actions/click-outside';
|
||||||
import { listNavigation } from '$lib/actions/list-navigation';
|
import { listNavigation } from '$lib/actions/list-navigation';
|
||||||
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
|
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';
|
||||||
import type Map from '$lib/components/shared-components/map/map.svelte';
|
import type Map from '$lib/components/shared-components/map/map.svelte';
|
||||||
import { timeDebounceOnSearch, timeToLoadTheMap } from '$lib/constants';
|
import { timeDebounceOnSearch, timeToLoadTheMap } from '$lib/constants';
|
||||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||||
import { lastChosenLocation } from '$lib/stores/asset-editor.store';
|
import { geolocationManager } from '$lib/managers/geolocation.manager.svelte';
|
||||||
|
import type { LatLng } from '$lib/types';
|
||||||
import { delay } from '$lib/utils/asset-utils';
|
import { delay } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
|
import { searchPlaces, type AssetResponseDto, type PlacesResponseDto } from '@immich/sdk';
|
||||||
import { ConfirmModal, LoadingSpinner } from '@immich/ui';
|
import { ConfirmModal, LoadingSpinner } from '@immich/ui';
|
||||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { get } from 'svelte/store';
|
|
||||||
interface Point {
|
|
||||||
lng: number;
|
|
||||||
lat: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
asset?: AssetResponseDto | undefined;
|
asset?: AssetResponseDto;
|
||||||
point?: Point;
|
point?: LatLng;
|
||||||
onClose: (point?: Point) => void;
|
onClose: (point?: LatLng) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { asset = undefined, point: initialPoint, onClose }: Props = $props();
|
let { asset, point: initialPoint, onClose }: Props = $props();
|
||||||
|
|
||||||
let places: PlacesResponseDto[] = $state([]);
|
let places: PlacesResponseDto[] = $state([]);
|
||||||
let suggestedPlaces: PlacesResponseDto[] = $derived(places.slice(0, 5));
|
let suggestedPlaces: PlacesResponseDto[] = $derived(places.slice(0, 5));
|
||||||
@@ -35,15 +32,22 @@
|
|||||||
let hideSuggestion = $state(false);
|
let hideSuggestion = $state(false);
|
||||||
let mapElement = $state<ReturnType<typeof Map>>();
|
let mapElement = $state<ReturnType<typeof Map>>();
|
||||||
|
|
||||||
let previousLocation = get(lastChosenLocation);
|
let assetPoint = $derived.by<LatLng | undefined>(() => {
|
||||||
|
if (!asset || !asset.exifInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let assetLat = $derived(initialPoint?.lat ?? asset?.exifInfo?.latitude ?? undefined);
|
const { latitude, longitude } = asset.exifInfo;
|
||||||
let assetLng = $derived(initialPoint?.lng ?? asset?.exifInfo?.longitude ?? undefined);
|
if (!isDefined(latitude) || !isDefined(longitude)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mapLat = $derived(assetLat ?? previousLocation?.lat ?? undefined);
|
return { lat: latitude, lng: longitude };
|
||||||
let mapLng = $derived(assetLng ?? previousLocation?.lng ?? undefined);
|
});
|
||||||
|
|
||||||
let zoom = $derived(mapLat && mapLng ? 12.5 : 1);
|
let point = $state<LatLng | undefined>(initialPoint ?? assetPoint);
|
||||||
|
let zoom = $state(point ? 12.5 : 1);
|
||||||
|
let center = $state(point ?? geolocationManager.lastPoint);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (mapElement && initialPoint) {
|
if (mapElement && initialPoint) {
|
||||||
@@ -57,11 +61,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let point: Point | null = $state(initialPoint ?? null);
|
|
||||||
|
|
||||||
const handleConfirm = (confirmed?: boolean) => {
|
const handleConfirm = (confirmed?: boolean) => {
|
||||||
if (point && confirmed) {
|
if (point && confirmed) {
|
||||||
lastChosenLocation.set(point);
|
geolocationManager.onSelected(point);
|
||||||
onClose(point);
|
onClose(point);
|
||||||
} else {
|
} else {
|
||||||
onClose();
|
onClose();
|
||||||
@@ -201,12 +203,12 @@
|
|||||||
{:then { default: Map }}
|
{:then { default: Map }}
|
||||||
<Map
|
<Map
|
||||||
bind:this={mapElement}
|
bind:this={mapElement}
|
||||||
mapMarkers={assetLat !== undefined && assetLng !== undefined && asset
|
mapMarkers={asset && assetPoint
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
lat: assetLat,
|
lat: assetPoint.lat,
|
||||||
lon: assetLng,
|
lon: assetPoint.lng,
|
||||||
city: asset.exifInfo?.city ?? null,
|
city: asset.exifInfo?.city ?? null,
|
||||||
state: asset.exifInfo?.state ?? null,
|
state: asset.exifInfo?.state ?? null,
|
||||||
country: asset.exifInfo?.country ?? null,
|
country: asset.exifInfo?.country ?? null,
|
||||||
@@ -214,7 +216,7 @@
|
|||||||
]
|
]
|
||||||
: []}
|
: []}
|
||||||
{zoom}
|
{zoom}
|
||||||
center={mapLat && mapLng ? { lat: mapLat, lng: mapLng } : undefined}
|
{center}
|
||||||
simplified={true}
|
simplified={true}
|
||||||
clickable={true}
|
clickable={true}
|
||||||
onClickPoint={(selected) => (point = selected)}
|
onClickPoint={(selected) => (point = selected)}
|
||||||
@@ -225,7 +227,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
|
<div class="grid sm:grid-cols-2 gap-4 text-sm text-start mt-4">
|
||||||
<CoordinatesInput lat={point ? point.lat : assetLat} lng={point ? point.lng : assetLng} {onUpdate} />
|
<CoordinatesInput lat={point?.lat} lng={point?.lng} {onUpdate} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import type { LatLng } from '$lib/types';
|
||||||
import { ConfirmModal } from '@immich/ui';
|
import { ConfirmModal } from '@immich/ui';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
location: { latitude: number | undefined; longitude: number | undefined };
|
point: LatLng;
|
||||||
assetCount: number;
|
assetCount: number;
|
||||||
onClose: (confirm: boolean) => void;
|
onClose: (confirm: boolean) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { location, assetCount, onClose }: Props = $props();
|
let { point, assetCount, onClose }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ConfirmModal title={$t('confirm')} size="small" confirmColor="primary" {onClose}>
|
<ConfirmModal title={$t('confirm')} size="small" confirmColor="primary" {onClose}>
|
||||||
{#snippet prompt()}
|
{#snippet prompt()}
|
||||||
<p>{$t('update_location_action_prompt', { values: { count: assetCount } })}</p>
|
<p>{$t('update_location_action_prompt', { values: { count: assetCount } })}</p>
|
||||||
<p>- {$t('latitude')}: {location.latitude}</p>
|
<p>- {$t('latitude')}: {point.lat}</p>
|
||||||
<p>- {$t('longitude')}: {location.longitude}</p>
|
<p>- {$t('longitude')}: {point.lng}</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</ConfirmModal>
|
</ConfirmModal>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
//-----other
|
|
||||||
export const lastChosenLocation = writable<{ lng: number; lat: number } | null>(null);
|
|
||||||
@@ -5,6 +5,8 @@ import type { ActionItem } from '@immich/ui';
|
|||||||
import type { DateTime } from 'luxon';
|
import type { DateTime } from 'luxon';
|
||||||
import type { SvelteSet } from 'svelte/reactivity';
|
import type { SvelteSet } from 'svelte/reactivity';
|
||||||
|
|
||||||
|
export type LatLng = { lng: number; lat: number };
|
||||||
|
|
||||||
export interface ReleaseEvent {
|
export interface ReleaseEvent {
|
||||||
isAvailable: boolean;
|
isAvailable: boolean;
|
||||||
/** ISO8601 */
|
/** ISO8601 */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { isDefined } from '$lib';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
|
||||||
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
|
||||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||||
import { AssetAction } from '$lib/constants';
|
import { AssetAction } from '$lib/constants';
|
||||||
@@ -8,8 +8,10 @@
|
|||||||
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
|
||||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
|
import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte';
|
||||||
import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte';
|
import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
|
import type { LatLng } from '$lib/types';
|
||||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||||
import { setQueryValue } from '$lib/utils/navigation';
|
import { setQueryValue } from '$lib/utils/navigation';
|
||||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||||
@@ -19,15 +21,15 @@
|
|||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
}
|
};
|
||||||
|
|
||||||
let { data }: Props = $props();
|
let { data }: Props = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let assetInteraction = new AssetInteraction();
|
let assetInteraction = new AssetInteraction();
|
||||||
let location = $state<{ latitude: number; longitude: number }>({ latitude: 0, longitude: 0 });
|
let point = $state<LatLng>();
|
||||||
let locationUpdated = $state(false);
|
let locationUpdated = $state(false);
|
||||||
|
|
||||||
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
let timelineManager = $state<TimelineManager>() as TimelineManager;
|
||||||
@@ -39,8 +41,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = async () => {
|
const handleUpdate = async () => {
|
||||||
|
if (!point) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, {
|
const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, {
|
||||||
location: location ?? { latitude: 0, longitude: 0 },
|
point,
|
||||||
assetCount: assetInteraction.selectedAssets.length,
|
assetCount: assetInteraction.selectedAssets.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,8 +57,8 @@
|
|||||||
await updateAssets({
|
await updateAssets({
|
||||||
assetBulkUpdateDto: {
|
assetBulkUpdateDto: {
|
||||||
ids: assetInteraction.selectedAssets.map((asset) => asset.id),
|
ids: assetInteraction.selectedAssets.map((asset) => asset.id),
|
||||||
latitude: location?.latitude ?? undefined,
|
latitude: point.lat,
|
||||||
longitude: location?.longitude ?? undefined,
|
longitude: point.lng,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -86,18 +92,13 @@
|
|||||||
cancelMultiselect(assetInteraction);
|
cancelMultiselect(assetInteraction);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePickOnMap = async () => {
|
const handlePickPoint = async () => {
|
||||||
const point = await modalManager.show(ChangeLocation, {
|
const selected = await modalManager.show(GeolocationPointPickerModal, { point });
|
||||||
point: {
|
if (!selected) {
|
||||||
lat: location.latitude,
|
|
||||||
lng: location.longitude,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!point) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = { latitude: point.lat, longitude: point.lng };
|
point = selected;
|
||||||
};
|
};
|
||||||
const handleEscape = () => {
|
const handleEscape = () => {
|
||||||
if (assetInteraction.selectionActive) {
|
if (assetInteraction.selectionActive) {
|
||||||
@@ -106,9 +107,10 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasGps = (asset: TimelineAsset) => {
|
type AssetPoint = { latitude: number; longitude: number };
|
||||||
return !!asset.latitude && !!asset.longitude;
|
|
||||||
};
|
const hasGps = (asset: TimelineAsset | AssetPoint): asset is AssetPoint =>
|
||||||
|
isDefined(asset.latitude) && isDefined(asset.longitude);
|
||||||
|
|
||||||
const handleThumbnailClick = (
|
const handleThumbnailClick = (
|
||||||
asset: TimelineAsset,
|
asset: TimelineAsset,
|
||||||
@@ -126,7 +128,7 @@
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
locationUpdated = false;
|
locationUpdated = false;
|
||||||
}, 1500);
|
}, 1500);
|
||||||
location = { latitude: asset.latitude!, longitude: asset.longitude! };
|
point = { lat: asset.latitude, lng: asset.longitude };
|
||||||
void setQueryValue('at', asset.id);
|
void setQueryValue('at', asset.id);
|
||||||
} else {
|
} else {
|
||||||
onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset);
|
onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset);
|
||||||
@@ -148,11 +150,17 @@
|
|||||||
title="latitude, longitude"
|
title="latitude, longitude"
|
||||||
class="rounded-3xl font-mono text-sm text-primary px-2 py-1 transition-all duration-100 ease-in-out {locationUpdated
|
class="rounded-3xl font-mono text-sm text-primary px-2 py-1 transition-all duration-100 ease-in-out {locationUpdated
|
||||||
? 'bg-primary/90 text-light font-semibold scale-105'
|
? 'bg-primary/90 text-light font-semibold scale-105'
|
||||||
: ''}">{location.latitude.toFixed(3)}, {location.longitude.toFixed(3)}</Text
|
: ''}"
|
||||||
>
|
>
|
||||||
|
{#if point}
|
||||||
|
{point.lat.toFixed(3)}, {point.lng.toFixed(3)}
|
||||||
|
{:else}
|
||||||
|
{$t('none')}
|
||||||
|
{/if}
|
||||||
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button size="small" color="secondary" variant="ghost" leadingIcon={mdiPencilOutline} onclick={handlePickOnMap}>
|
<Button size="small" color="secondary" variant="ghost" leadingIcon={mdiPencilOutline} onclick={handlePickPoint}>
|
||||||
<Text class="hidden sm:inline-block">{$t('location_picker_choose_on_map')}</Text>
|
<Text class="hidden sm:inline-block">{$t('location_picker_choose_on_map')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user