mirror of
https://github.com/immich-app/immich.git
synced 2025-09-29 15:31:13 -04:00
fix: navigate to time action
This commit is contained in:
parent
ba0cfb76ed
commit
d5cc017bba
@ -1360,6 +1360,8 @@
|
|||||||
"my_albums": "My albums",
|
"my_albums": "My albums",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"name_or_nickname": "Name or nickname",
|
"name_or_nickname": "Name or nickname",
|
||||||
|
"navigate": "Navigate",
|
||||||
|
"navigate_to_time": "Navigate to Time",
|
||||||
"network_requirement_photos_upload": "Use cellular data to backup photos",
|
"network_requirement_photos_upload": "Use cellular data to backup photos",
|
||||||
"network_requirement_videos_upload": "Use cellular data to backup videos",
|
"network_requirement_videos_upload": "Use cellular data to backup videos",
|
||||||
"network_requirements": "Network Requirements",
|
"network_requirements": "Network Requirements",
|
||||||
|
@ -8,6 +8,9 @@ import ChangeDate from './change-date.svelte';
|
|||||||
describe('ChangeDate component', () => {
|
describe('ChangeDate component', () => {
|
||||||
const initialDate = DateTime.fromISO('2024-01-01');
|
const initialDate = DateTime.fromISO('2024-01-01');
|
||||||
const initialTimeZone = 'Europe/Berlin';
|
const initialTimeZone = 'Europe/Berlin';
|
||||||
|
const targetDate = DateTime.fromISO('2024-01-01').setZone('UTC+1', {
|
||||||
|
keepLocalTime: true,
|
||||||
|
});
|
||||||
const currentInterval = {
|
const currentInterval = {
|
||||||
start: DateTime.fromISO('2000-02-01T14:00:00+01:00'),
|
start: DateTime.fromISO('2000-02-01T14:00:00+01:00'),
|
||||||
end: DateTime.fromISO('2001-02-01T14:00:00+01:00'),
|
end: DateTime.fromISO('2001-02-01T14:00:00+01:00'),
|
||||||
@ -50,7 +53,11 @@ describe('ChangeDate component', () => {
|
|||||||
|
|
||||||
await fireEvent.click(getConfirmButton());
|
await fireEvent.click(getConfirmButton());
|
||||||
|
|
||||||
expect(onConfirm).toHaveBeenCalledWith({ mode: 'absolute', date: '2024-01-01T00:00:00.000+01:00' });
|
expect(onConfirm).toHaveBeenCalledWith({
|
||||||
|
mode: 'absolute',
|
||||||
|
date: '2024-01-01T00:00:00.000+01:00',
|
||||||
|
dateTime: targetDate,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('calls onCancel on cancel', async () => {
|
test('calls onCancel on cancel', async () => {
|
||||||
@ -65,7 +72,9 @@ describe('ChangeDate component', () => {
|
|||||||
|
|
||||||
describe('when date is in daylight saving time', () => {
|
describe('when date is in daylight saving time', () => {
|
||||||
const dstDate = DateTime.fromISO('2024-07-01');
|
const dstDate = DateTime.fromISO('2024-07-01');
|
||||||
|
const targetDate = DateTime.fromISO('2024-07-01').setZone('UTC+2', {
|
||||||
|
keepLocalTime: true,
|
||||||
|
});
|
||||||
test('should render correct timezone with offset', () => {
|
test('should render correct timezone with offset', () => {
|
||||||
render(ChangeDate, { initialDate: dstDate, initialTimeZone, onCancel, onConfirm });
|
render(ChangeDate, { initialDate: dstDate, initialTimeZone, onCancel, onConfirm });
|
||||||
|
|
||||||
@ -79,7 +88,11 @@ describe('ChangeDate component', () => {
|
|||||||
|
|
||||||
await fireEvent.click(getConfirmButton());
|
await fireEvent.click(getConfirmButton());
|
||||||
|
|
||||||
expect(onConfirm).toHaveBeenCalledWith({ mode: 'absolute', date: '2024-07-01T00:00:00.000+02:00' });
|
expect(onConfirm).toHaveBeenCalledWith({
|
||||||
|
mode: 'absolute',
|
||||||
|
date: '2024-07-01T00:00:00.000+02:00',
|
||||||
|
dateTime: targetDate,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { getDateTimeOffsetLocaleString } from '$lib/utils/timeline-util.js';
|
import { getDateTimeOffsetLocaleString } from '$lib/utils/timeline-util.js';
|
||||||
import { ConfirmModal, Field, Switch } from '@immich/ui';
|
import { ConfirmModal, Field, Switch } from '@immich/ui';
|
||||||
import { mdiCalendarEditOutline } from '@mdi/js';
|
import { mdiCalendarEdit } from '@mdi/js';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
@ -17,6 +17,8 @@
|
|||||||
timezoneInput?: boolean;
|
timezoneInput?: boolean;
|
||||||
withDuration?: boolean;
|
withDuration?: boolean;
|
||||||
currentInterval?: { start: DateTime; end: DateTime };
|
currentInterval?: { start: DateTime; end: DateTime };
|
||||||
|
icon?: string;
|
||||||
|
confirmText?: string;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
onConfirm: (result: AbsoluteResult | RelativeResult) => void;
|
onConfirm: (result: AbsoluteResult | RelativeResult) => void;
|
||||||
}
|
}
|
||||||
@ -28,6 +30,8 @@
|
|||||||
timezoneInput = true,
|
timezoneInput = true,
|
||||||
withDuration = true,
|
withDuration = true,
|
||||||
currentInterval = undefined,
|
currentInterval = undefined,
|
||||||
|
icon = mdiCalendarEdit,
|
||||||
|
confirmText,
|
||||||
onCancel,
|
onCancel,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
@ -35,6 +39,7 @@
|
|||||||
export type AbsoluteResult = {
|
export type AbsoluteResult = {
|
||||||
mode: 'absolute';
|
mode: 'absolute';
|
||||||
date: string;
|
date: string;
|
||||||
|
dateTime: DateTime<true>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RelativeResult = {
|
export type RelativeResult = {
|
||||||
@ -191,9 +196,15 @@
|
|||||||
const fixedOffsetZone = `UTC${offsetMinutes >= 0 ? '+' : ''}${Duration.fromObject({ minutes: offsetMinutes }).toFormat('hh:mm')}`;
|
const fixedOffsetZone = `UTC${offsetMinutes >= 0 ? '+' : ''}${Duration.fromObject({ minutes: offsetMinutes }).toFormat('hh:mm')}`;
|
||||||
|
|
||||||
// Create a DateTime object in this fixed-offset zone, preserving the local time.
|
// Create a DateTime object in this fixed-offset zone, preserving the local time.
|
||||||
const finalDateTime = DateTime.fromObject(dtComponents.toObject(), { zone: fixedOffsetZone });
|
const fixedOffsetDateTime = DateTime.fromObject(dtComponents.toObject(), {
|
||||||
|
zone: fixedOffsetZone,
|
||||||
|
}) as DateTime<true>;
|
||||||
|
|
||||||
onConfirm({ mode: 'absolute', date: finalDateTime.toISO({ includeOffset: true })! });
|
onConfirm({
|
||||||
|
mode: 'absolute',
|
||||||
|
date: fixedOffsetDateTime.toISO({ includeOffset: true })!,
|
||||||
|
dateTime: fixedOffsetDateTime,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showRelative && (selectedDuration || selectedRelativeOption)) {
|
if (showRelative && (selectedDuration || selectedRelativeOption)) {
|
||||||
@ -237,7 +248,8 @@
|
|||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
confirmColor="primary"
|
confirmColor="primary"
|
||||||
{title}
|
{title}
|
||||||
icon={mdiCalendarEditOutline}
|
{icon}
|
||||||
|
{confirmText}
|
||||||
prompt="Please select a new date:"
|
prompt="Please select a new date:"
|
||||||
disabled={!date.isValid}
|
disabled={!date.isValid}
|
||||||
onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
|
onClose={(confirmed) => (confirmed ? handleConfirm() : onCancel())}
|
||||||
|
@ -833,15 +833,14 @@
|
|||||||
title="Navigate to Time"
|
title="Navigate to Time"
|
||||||
initialDate={DateTime.now()}
|
initialDate={DateTime.now()}
|
||||||
timezoneInput={false}
|
timezoneInput={false}
|
||||||
onConfirm={async (dateString: AbsoluteResult | RelativeResult) => {
|
onConfirm={async (result: AbsoluteResult | RelativeResult) => {
|
||||||
isShowSelectDate = false;
|
isShowSelectDate = false;
|
||||||
if (dateString.mode == 'absolute') {
|
if (result.mode !== 'absolute') {
|
||||||
const asset = await timelineManager.getClosestAssetToDate(
|
return;
|
||||||
(DateTime.fromISO(dateString.date) as DateTime<true>).toObject(),
|
}
|
||||||
);
|
const asset = await timelineManager.getClosestAssetToDate(result.dateTime.toObject());
|
||||||
if (asset) {
|
if (asset) {
|
||||||
setFocusAsset(asset);
|
setFocusAsset(asset);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onCancel={() => (isShowSelectDate = false)}
|
onCancel={() => (isShowSelectDate = false)}
|
||||||
|
@ -143,3 +143,24 @@ export function findMonthGroupForDate(timelineManager: TimelineManager, targetYe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findClosestGroupForDate(timelineManager: TimelineManager, targetYearMonth: TimelineYearMonth) {
|
||||||
|
let closestMonth: MonthGroup | undefined;
|
||||||
|
let minDifference = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
for (const month of timelineManager.months) {
|
||||||
|
const { year, month: monthNum } = month.yearMonth;
|
||||||
|
|
||||||
|
// Calculate the absolute difference in months
|
||||||
|
const yearDiff = Math.abs(year - targetYearMonth.year);
|
||||||
|
const monthDiff = Math.abs(monthNum - targetYearMonth.month);
|
||||||
|
const totalDiff = yearDiff * 12 + monthDiff;
|
||||||
|
|
||||||
|
if (totalDiff < minDifference) {
|
||||||
|
minDifference = totalDiff;
|
||||||
|
closestMonth = month;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestMonth;
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
runAssetOperation,
|
runAssetOperation,
|
||||||
} from '$lib/managers/timeline-manager/internal/operations-support.svelte';
|
} from '$lib/managers/timeline-manager/internal/operations-support.svelte';
|
||||||
import {
|
import {
|
||||||
|
findClosestGroupForDate,
|
||||||
findMonthGroupForAsset as findMonthGroupForAssetUtil,
|
findMonthGroupForAsset as findMonthGroupForAssetUtil,
|
||||||
findMonthGroupForDate,
|
findMonthGroupForDate,
|
||||||
getAssetWithOffset,
|
getAssetWithOffset,
|
||||||
@ -523,9 +524,13 @@ export class TimelineManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getClosestAssetToDate(dateTime: TimelineDateTime) {
|
async getClosestAssetToDate(dateTime: TimelineDateTime) {
|
||||||
const monthGroup = findMonthGroupForDate(this, dateTime);
|
let monthGroup = findMonthGroupForDate(this, dateTime);
|
||||||
if (!monthGroup) {
|
if (!monthGroup) {
|
||||||
return;
|
// if exact match not found, find closest
|
||||||
|
monthGroup = findClosestGroupForDate(this, dateTime);
|
||||||
|
if (!monthGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await this.loadMonthGroup(dateTime, { cancelable: false });
|
await this.loadMonthGroup(dateTime, { cancelable: false });
|
||||||
const asset = monthGroup.findClosest(dateTime);
|
const asset = monthGroup.findClosest(dateTime);
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
{ key: ['D', 'd'], action: $t('previous_or_next_day') },
|
{ key: ['D', 'd'], action: $t('previous_or_next_day') },
|
||||||
{ key: ['M', 'm'], action: $t('previous_or_next_month') },
|
{ key: ['M', 'm'], action: $t('previous_or_next_month') },
|
||||||
{ key: ['Y', 'y'], action: $t('previous_or_next_year') },
|
{ key: ['Y', 'y'], action: $t('previous_or_next_year') },
|
||||||
|
{ key: ['g'], action: $t('navigate_to_time') },
|
||||||
{ key: ['x'], action: $t('select') },
|
{ key: ['x'], action: $t('select') },
|
||||||
{ key: ['Esc'], action: $t('back_close_deselect') },
|
{ key: ['Esc'], action: $t('back_close_deselect') },
|
||||||
{ key: ['Ctrl', 'k'], action: $t('search_your_photos') },
|
{ key: ['Ctrl', 'k'], action: $t('search_your_photos') },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user