mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
ui changes
This commit is contained in:
parent
0ee9cc6924
commit
feaf95e791
3 changed files with 122 additions and 8 deletions
|
|
@ -1029,6 +1029,7 @@
|
||||||
"unable_to_update_album_info": "Unable to update album info",
|
"unable_to_update_album_info": "Unable to update album info",
|
||||||
"unable_to_update_library": "Unable to update library",
|
"unable_to_update_library": "Unable to update library",
|
||||||
"unable_to_update_location": "Unable to update location",
|
"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_settings": "Unable to update settings",
|
||||||
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
|
"unable_to_update_timeline_display_status": "Unable to update timeline display status",
|
||||||
"unable_to_update_user": "Unable to update user",
|
"unable_to_update_user": "Unable to update user",
|
||||||
|
|
@ -1467,6 +1468,11 @@
|
||||||
"partner_page_shared_to_title": "Shared to",
|
"partner_page_shared_to_title": "Shared to",
|
||||||
"partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.",
|
"partner_page_stop_sharing_content": "{partner} will no longer be able to access your photos.",
|
||||||
"partner_sharing": "Partner Sharing",
|
"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",
|
"partners": "Partners",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"password_does_not_match": "Password does not match",
|
"password_does_not_match": "Password does not match",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.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 PartnerSelectionModal from '$lib/modals/PartnerSelectionModal.svelte';
|
||||||
import {
|
import {
|
||||||
createPartner,
|
createPartner,
|
||||||
|
|
@ -11,8 +12,8 @@
|
||||||
type PartnerResponseDto,
|
type PartnerResponseDto,
|
||||||
type UserResponseDto,
|
type UserResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { Button, Icon, IconButton, modalManager } from '@immich/ui';
|
import { Button, Icon, IconButton, modalManager, Field } from '@immich/ui';
|
||||||
import { mdiCheck, mdiClose } from '@mdi/js';
|
import { mdiCheck, mdiClose, mdiPencil } from '@mdi/js';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
sharedByMe: boolean;
|
sharedByMe: boolean;
|
||||||
sharedWithMe: boolean;
|
sharedWithMe: boolean;
|
||||||
inTimeline: boolean;
|
inTimeline: boolean;
|
||||||
|
startDate?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -31,6 +33,8 @@
|
||||||
let { user }: Props = $props();
|
let { user }: Props = $props();
|
||||||
|
|
||||||
let partners: Array<PartnerSharing> = $state([]);
|
let partners: Array<PartnerSharing> = $state([]);
|
||||||
|
let editingStartDate: Record<string, boolean> = $state({});
|
||||||
|
let tempStartDates: Record<string, string | undefined> = $state({});
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
await refreshPartners();
|
await refreshPartners();
|
||||||
|
|
@ -52,6 +56,7 @@
|
||||||
sharedByMe: true,
|
sharedByMe: true,
|
||||||
sharedWithMe: false,
|
sharedWithMe: false,
|
||||||
inTimeline: candidate.inTimeline ?? false,
|
inTimeline: candidate.inTimeline ?? false,
|
||||||
|
startDate: candidate.startDate,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -67,11 +72,13 @@
|
||||||
sharedByMe: false,
|
sharedByMe: false,
|
||||||
sharedWithMe: true,
|
sharedWithMe: true,
|
||||||
inTimeline: candidate.inTimeline ?? false,
|
inTimeline: candidate.inTimeline ?? false,
|
||||||
|
startDate: candidate.startDate,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
partners[existIndex].sharedWithMe = true;
|
partners[existIndex].sharedWithMe = true;
|
||||||
partners[existIndex].inTimeline = candidate.inTimeline ?? false;
|
partners[existIndex].inTimeline = candidate.inTimeline ?? false;
|
||||||
|
partners[existIndex].startDate = candidate.startDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -95,15 +102,26 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreatePartners = async () => {
|
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) {
|
if (!users) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const user of users) {
|
for (const selectedUser of users) {
|
||||||
await createPartner({ partnerCreateDto: { sharedWithId: user.id } });
|
await createPartner({
|
||||||
|
partnerCreateDto: {
|
||||||
|
sharedWithId: selectedUser.id,
|
||||||
|
startDate: startDate || undefined,
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await refreshPartners();
|
await refreshPartners();
|
||||||
|
|
@ -121,6 +139,43 @@
|
||||||
handleError(error, $t('errors.unable_to_update_timeline_display_status'));
|
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>
|
</script>
|
||||||
|
|
||||||
<section class="my-4">
|
<section class="my-4">
|
||||||
|
|
@ -171,6 +226,45 @@
|
||||||
{$t('partner_can_access_location')}
|
{$t('partner_can_access_location')}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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}
|
{/if}
|
||||||
|
|
||||||
<!-- this user is sharing assets with me -->
|
<!-- this user is sharing assets with me -->
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
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 { 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 { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: UserResponseDto;
|
user: UserResponseDto;
|
||||||
onClose: (users?: UserResponseDto[]) => void;
|
onClose: (users?: UserResponseDto[], startDate?: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { user, onClose }: Props = $props();
|
let { user, onClose }: Props = $props();
|
||||||
|
|
||||||
let availableUsers: UserResponseDto[] = $state([]);
|
let availableUsers: UserResponseDto[] = $state([]);
|
||||||
let selectedUsers: UserResponseDto[] = $state([]);
|
let selectedUsers: UserResponseDto[] = $state([]);
|
||||||
|
let startDate: string | undefined = $state(undefined);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let users = await searchUsers();
|
let users = await searchUsers();
|
||||||
|
|
@ -69,9 +71,21 @@
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/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>
|
<ModalFooter>
|
||||||
{#if selectedUsers.length > 0}
|
{#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}
|
{/if}
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue