Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): coordinate input for asset location #11291

Merged
merged 1 commit into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
import { act, render, type RenderResult } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';

describe('NumberRangeInput component', () => {
const user = userEvent.setup();
let sut: RenderResult<NumberRangeInput>;
let input: HTMLInputElement;

beforeEach(() => {
sut = render(NumberRangeInput, { id: '', min: -90, max: 90, onInput: () => {} });
input = sut.getByRole('spinbutton') as HTMLInputElement;
});

it('updates value', async () => {
expect(input.value).toBe('');
await act(() => sut.component.$set({ value: 10 }));
expect(input.value).toBe('10');
});

it('restricts minimum value', async () => {
await user.type(input, '-91');
expect(input.value).toBe('-90');
});

it('restricts maximum value', async () => {
await user.type(input, '09990');
expect(input.value).toBe('90');
});

it('allows entering negative numbers', async () => {
await user.type(input, '-10');
expect(input.value).toBe('-10');
});

it('allows entering zero', async () => {
await user.type(input, '0');
expect(input.value).toBe('0');
});

it('allows entering decimal numbers', async () => {
await user.type(input, '-0.09001');
expect(input.value).toBe('-0.09001');
});

it('ignores text input', async () => {
await user.type(input, 'test');
expect(input.value).toBe('');
});
});
27 changes: 19 additions & 8 deletions web/src/lib/components/shared-components/change-location.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import SearchBar from '../elements/search-bar.svelte';
import { listNavigation } from '$lib/actions/list-navigation';
import { t } from 'svelte-i18n';
import CoordinatesInput from '$lib/components/shared-components/coordinates-input.svelte';

export let asset: AssetResponseDto | undefined = undefined;

Expand All @@ -34,9 +35,9 @@
confirm: Point;
}>();

$: lat = asset?.exifInfo?.latitude || 0;
$: lng = asset?.exifInfo?.longitude || 0;
$: zoom = lat && lng ? 15 : 1;
$: lat = asset?.exifInfo?.latitude ?? undefined;
$: lng = asset?.exifInfo?.longitude ?? undefined;
$: zoom = lat !== undefined && lng !== undefined ? 15 : 1;

$: {
if (places) {
Expand Down Expand Up @@ -148,7 +149,7 @@
{/if}
</div>
</div>
<label for="datetime">{$t('pick_a_location')}</label>
<span>{$t('pick_a_location')}</span>
<div class="h-[500px] min-h-[300px] w-full">
{#await import('../shared-components/map/map.svelte')}
{#await delay(timeToLoadTheMap) then}
Expand All @@ -157,10 +158,9 @@
<LoadingSpinner />
</div>
{/await}
{:then component}
<svelte:component
this={component.default}
mapMarkers={lat && lng && asset
{:then { default: Map }}
<Map
mapMarkers={lat !== undefined && lng !== undefined && asset
? [
{
id: asset.id,
Expand All @@ -181,5 +181,16 @@
/>
{/await}
</div>

<div class="grid sm:grid-cols-2 gap-4 text-sm text-left mt-4">
<CoordinatesInput
lat={point ? point.lat : lat}
lng={point ? point.lng : lng}
onUpdate={(lat, lng) => {
point = { lat, lng };
addClipMapMarker(lng, lat);
}}
/>
</div>
</div>
</ConfirmDialog>
27 changes: 27 additions & 0 deletions web/src/lib/components/shared-components/coordinates-input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
import { generateId } from '$lib/utils/generate-id';
import { t } from 'svelte-i18n';

export let lat: number | null | undefined = undefined;
export let lng: number | null | undefined = undefined;
export let onUpdate: (lat: number, lng: number) => void;

const id = generateId();

const onInput = () => {
if (lat != null && lng != null) {
onUpdate(lat, lng);
}
};
</script>

<div>
<label class="immich-form-label" for="latitude-input-{id}">{$t('latitude')}</label>
<NumberRangeInput id="latitude-input-{id}" min={-90} max={90} {onInput} bind:value={lat} />
</div>

<div>
<label class="immich-form-label" for="longitude-input-{id}">{$t('longitude')}</label>
<NumberRangeInput id="longitude-input-{id}" min={-180} max={180} {onInput} bind:value={lng} />
</div>
28 changes: 28 additions & 0 deletions web/src/lib/components/shared-components/number-range-input.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import { clamp } from 'lodash-es';

export let id: string;
export let min: number;
export let max: number;
export let step: number | string = 'any';
export let required = true;
export let value: number | null = null;
export let onInput: (value: number | null) => void;
</script>

<input
type="number"
class="immich-form-input w-full"
{id}
{min}
{max}
{step}
{required}
bind:value
on:input={() => {
if (value !== null && (value < min || value > max)) {
value = clamp(value, min, max);
}
onInput(value);
}}
/>
2 changes: 2 additions & 0 deletions web/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,7 @@
"language_setting_description": "Select your preferred language",
"last_seen": "Last seen",
"latest_version": "Latest Version",
"latitude": "Latitude",
"leave": "Leave",
"let_others_respond": "Let others respond",
"level": "Level",
Expand Down Expand Up @@ -786,6 +787,7 @@
"login_has_been_disabled": "Login has been disabled.",
"logout_all_device_confirmation": "Are you sure you want to log out all devices?",
"logout_this_device_confirmation": "Are you sure you want to log out this device?",
"longitude": "Longitude",
"look": "Look",
"loop_videos": "Loop videos",
"loop_videos_description": "Enable to automatically loop a video in the detail viewer.",
Expand Down
Loading