ui changes

This commit is contained in:
Julien Robert 2025-10-14 12:08:51 +02:00
parent 0ee9cc6924
commit feaf95e791
No known key found for this signature in database
GPG key ID: AEA8C21E608F1D02
3 changed files with 122 additions and 8 deletions

View file

@ -1029,6 +1029,7 @@
"unable_to_update_album_info": "Unable to update album info",
"unable_to_update_library": "Unable to update library",
"unable_to_update_location": "Unable to update location",
"unable_to_update_partner": "Unable to update partner",
"unable_to_update_settings": "Unable to update settings",
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
"unable_to_update_user": "Unable to update user",
@ -1467,6 +1468,11 @@
"partner_page_shared_to_title": "Shared to",
"partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.",
"partner_sharing": "Partner Sharing",
"partner_sharing_start_date": "Share photos from",
"partner_sharing_start_date_description": "Only photos taken on or after this date will be shared. Leave empty to share all photos.",
"partner_sharing_start_date_help": "Photos taken on or after {date} are shared",
"partner_sharing_start_date_none": "All photos are shared (no date restriction)",
"partner_sharing_start_date_optional": "Optional: Restrict sharing to photos from a specific date",
"partners": "Partners",
"password": "Password",
"password_does_not_match": "Password does not match",

View file

@ -1,6 +1,7 @@
<script lang="ts">
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import DateInput from '$lib/elements/DateInput.svelte';
import PartnerSelectionModal from '$lib/modals/PartnerSelectionModal.svelte';
import {
createPartner,
@ -11,8 +12,8 @@
type PartnerResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { Button, Icon, IconButton, modalManager } from '@immich/ui';
import { mdiCheck, mdiClose } from '@mdi/js';
import { Button, Icon, IconButton, modalManager, Field } from '@immich/ui';
import { mdiCheck, mdiClose, mdiPencil } from '@mdi/js';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { handleError } from '../../utils/handle-error';
@ -22,6 +23,7 @@
sharedByMe: boolean;
sharedWithMe: boolean;
inTimeline: boolean;
startDate?: string | null;
}
interface Props {
@ -31,6 +33,8 @@
let { user }: Props = $props();
let partners: Array<PartnerSharing> = $state([]);
let editingStartDate: Record<string, boolean> = $state({});
let tempStartDates: Record<string, string | undefined> = $state({});
onMount(async () => {
await refreshPartners();
@ -52,6 +56,7 @@
sharedByMe: true,
sharedWithMe: false,
inTimeline: candidate.inTimeline ?? false,
startDate: candidate.startDate,
},
];
}
@ -67,11 +72,13 @@
sharedByMe: false,
sharedWithMe: true,
inTimeline: candidate.inTimeline ?? false,
startDate: candidate.startDate,
},
];
} else {
partners[existIndex].sharedWithMe = true;
partners[existIndex].inTimeline = candidate.inTimeline ?? false;
partners[existIndex].startDate = candidate.startDate;
}
}
};
@ -95,15 +102,26 @@
};
const handleCreatePartners = async () => {
const users = await modalManager.show(PartnerSelectionModal, { user });
const result = await modalManager.show(PartnerSelectionModal, { user });
if (!result) {
return;
}
const [users, startDate] = result;
if (!users) {
return;
}
try {
for (const user of users) {
await createPartner({ partnerCreateDto: { sharedWithId: user.id } });
for (const selectedUser of users) {
await createPartner({
partnerCreateDto: {
sharedWithId: selectedUser.id,
startDate: startDate || undefined,
}
});
}
await refreshPartners();
@ -121,6 +139,43 @@
handleError(error, $t('errors.unable_to_update_timeline_display_status'));
}
};
const handleEditStartDate = (partnerId: string, currentDate?: string | null) => {
editingStartDate[partnerId] = true;
tempStartDates[partnerId] = currentDate ? currentDate.split('T')[0] : undefined;
};
const handleSaveStartDate = async (partner: PartnerSharing) => {
try {
const newDate = tempStartDates[partner.user.id];
await updatePartner({
id: partner.user.id,
partnerUpdateDto: {
inTimeline: partner.inTimeline,
startDate: newDate || null,
}
});
partner.startDate = newDate || null;
editingStartDate[partner.user.id] = false;
} catch (error) {
handleError(error, $t('errors.unable_to_update_partner'));
}
};
const handleCancelEditStartDate = (partnerId: string) => {
editingStartDate[partnerId] = false;
delete tempStartDates[partnerId];
};
const formatDate = (dateString?: string | null) => {
if (!dateString) return null;
try {
return new Date(dateString).toLocaleDateString();
} catch {
return null;
}
};
</script>
<section class="my-4">
@ -171,6 +226,45 @@
{$t('partner_can_access_location')}
</li>
</ul>
<div class="mt-4">
{#if editingStartDate[partner.user.id]}
<Field label={$t('partner_sharing_start_date')} description={$t('partner_sharing_start_date_description')}>
<div class="flex gap-2 items-center mt-2">
<DateInput
class="immich-form-input flex-1"
id="partner-start-date-{partner.user.id}"
type="date"
bind:value={tempStartDates[partner.user.id]}
/>
<Button size="small" onclick={() => handleSaveStartDate(partner)}>{$t('save')}</Button>
<Button size="small" color="secondary" onclick={() => handleCancelEditStartDate(partner.user.id)}>{$t('cancel')}</Button>
</div>
</Field>
{:else}
<div class="flex items-center justify-between py-2">
<div class="flex-1">
<p class="text-sm font-medium">{$t('partner_sharing_start_date')}</p>
<p class="text-xs text-gray-600 dark:text-gray-400">
{#if partner.startDate}
{$t('partner_sharing_start_date_help', { values: { date: formatDate(partner.startDate) } })}
{:else}
{$t('partner_sharing_start_date_none')}
{/if}
</p>
</div>
<IconButton
shape="round"
color="secondary"
variant="ghost"
onclick={() => handleEditStartDate(partner.user.id, partner.startDate)}
icon={mdiPencil}
size="small"
aria-label={$t('edit')}
/>
</div>
{/if}
</div>
{/if}
<!-- this user is sharing assets with me -->

View file

@ -1,19 +1,21 @@
<script lang="ts">
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import DateInput from '$lib/elements/DateInput.svelte';
import { getPartners, PartnerDirection, searchUsers, type UserResponseDto } from '@immich/sdk';
import { Button, Modal, ModalBody, ModalFooter } from '@immich/ui';
import { Button, Modal, ModalBody, ModalFooter, Field } from '@immich/ui';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
interface Props {
user: UserResponseDto;
onClose: (users?: UserResponseDto[]) => void;
onClose: (users?: UserResponseDto[], startDate?: string | null) => void;
}
let { user, onClose }: Props = $props();
let availableUsers: UserResponseDto[] = $state([]);
let selectedUsers: UserResponseDto[] = $state([]);
let startDate: string | undefined = $state(undefined);
onMount(async () => {
let users = await searchUsers();
@ -69,9 +71,21 @@
</p>
{/if}
<div class="mt-4 px-5">
<Field label={$t('partner_sharing_start_date_optional')} description={$t('partner_sharing_start_date_description')}>
<DateInput
class="immich-form-input mt-2"
id="partner-start-date"
type="date"
bind:value={startDate}
placeholder={$t('partner_sharing_start_date')}
/>
</Field>
</div>
<ModalFooter>
{#if selectedUsers.length > 0}
<Button shape="round" fullWidth onclick={() => onClose(selectedUsers)}>{$t('add')}</Button>
<Button shape="round" fullWidth onclick={() => onClose(selectedUsers, startDate || null)}>{$t('add')}</Button>
{/if}
</ModalFooter>
</div>