feat(web): unlink live photos (#12574)

feat(web): unlink live photo
This commit is contained in:
Jason Rasmussen 2024-09-11 16:26:29 -04:00 committed by GitHub
parent 233372303b
commit 01c7adc24d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 148 additions and 37 deletions

View file

@ -1,44 +1,75 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import type { OnLink } from '$lib/utils/actions';
import { AssetTypeEnum, updateAsset } from '@immich/sdk';
import { mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js';
import type { OnLink, OnUnlink } from '$lib/utils/actions';
import { handleError } from '$lib/utils/handle-error';
import { AssetTypeEnum, getAssetInfo, updateAsset } from '@immich/sdk';
import { mdiLinkOff, mdiMotionPlayOutline, mdiTimerSand } from '@mdi/js';
import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
export let onLink: OnLink;
export let onUnlink: OnUnlink;
export let menuItem = false;
export let unlink = false;
let loading = false;
const text = $t('link_motion_video');
const icon = mdiMotionPlayOutline;
$: text = unlink ? $t('unlink_motion_video') : $t('link_motion_video');
$: icon = unlink ? mdiLinkOff : mdiMotionPlayOutline;
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const onClick = () => (unlink ? handleUnlink() : handleLink());
const handleLink = async () => {
let [still, motion] = [...getOwnedAssets()];
if (still.type === AssetTypeEnum.Video) {
[still, motion] = [motion, still];
}
loading = true;
const response = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: motion.id } });
onLink(response);
clearSelect();
loading = false;
try {
loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: motion.id } });
onLink({ still: stillResponse, motion });
clearSelect();
} catch (error) {
handleError(error, $t('errors.unable_to_link_motion_video'));
} finally {
loading = false;
}
};
const handleUnlink = async () => {
const [still] = [...getOwnedAssets()];
const motionId = still?.livePhotoVideoId;
if (!motionId) {
return;
}
try {
loading = true;
const stillResponse = await updateAsset({ id: still.id, updateAssetDto: { livePhotoVideoId: null } });
const motionResponse = await getAssetInfo({ id: motionId });
onUnlink({ still: stillResponse, motion: motionResponse });
clearSelect();
} catch (error) {
handleError(error, $t('errors.unable_to_unlink_motion_video'));
} finally {
loading = false;
}
};
</script>
{#if menuItem}
<MenuOption {text} {icon} onClick={handleLink} />
<MenuOption {text} {icon} {onClick} />
{/if}
{#if !menuItem}
{#if loading}
<CircleIconButton title={$t('loading')} icon={mdiTimerSand} />
{:else}
<CircleIconButton title={text} {icon} on:click={handleLink} />
<CircleIconButton title={text} {icon} on:click={onClick} />
{/if}
{/if}

View file

@ -641,6 +641,7 @@
"unable_to_get_comments_number": "Unable to get number of comments",
"unable_to_get_shared_link": "Failed to get shared link",
"unable_to_hide_person": "Unable to hide person",
"unable_to_link_motion_video": "Unable to link motion video",
"unable_to_link_oauth_account": "Unable to link OAuth account",
"unable_to_load_album": "Unable to load album",
"unable_to_load_asset_activity": "Unable to load asset activity",
@ -679,6 +680,7 @@
"unable_to_submit_job": "Unable to submit job",
"unable_to_trash_asset": "Unable to trash asset",
"unable_to_unlink_account": "Unable to unlink account",
"unable_to_unlink_motion_video": "Unable to unlink motion video",
"unable_to_update_album_cover": "Unable to update album cover",
"unable_to_update_album_info": "Unable to update album info",
"unable_to_update_library": "Unable to update library",
@ -1219,6 +1221,7 @@
"unknown": "Unknown",
"unknown_year": "Unknown Year",
"unlimited": "Unlimited",
"unlink_motion_video": "Unlink motion video",
"unlink_oauth": "Unlink OAuth",
"unlinked_oauth_account": "Unlinked OAuth account",
"unnamed_album": "Unnamed Album",

View file

@ -6,7 +6,8 @@ import { handleError } from './handle-error';
export type OnDelete = (assetIds: string[]) => void;
export type OnRestore = (ids: string[]) => void;
export type OnLink = (asset: AssetResponseDto) => void;
export type OnLink = (assets: { still: AssetResponseDto; motion: AssetResponseDto }) => void;
export type OnUnlink = (assets: { still: AssetResponseDto; motion: AssetResponseDto }) => void;
export type OnArchive = (ids: string[], isArchived: boolean) => void;
export type OnFavorite = (ids: string[], favorite: boolean) => void;
export type OnStack = (ids: string[]) => void;

View file

@ -23,8 +23,9 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore } from '$lib/stores/assets.store';
import { preferences, user } from '$lib/stores/user.store';
import type { OnLink, OnUnlink } from '$lib/utils/actions';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
import { AssetTypeEnum } from '@immich/sdk';
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
@ -35,12 +36,21 @@
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
let isAllFavorite: boolean;
let isAllOwned: boolean;
let isAssetStackSelected: boolean;
let isLinkActionAvailable: boolean;
$: {
const selection = [...$selectedAssets];
isAllOwned = selection.every((asset) => asset.ownerId === $user.id);
isAllFavorite = selection.every((asset) => asset.isFavorite);
isAssetStackSelected = selection.length === 1 && !!selection[0].stack;
const isLivePhoto = selection.length === 1 && !!selection[0].livePhotoVideoId;
const isLivePhotoCandidate =
selection.length === 2 &&
selection.some((asset) => asset.type === AssetTypeEnum.Image) &&
selection.some((asset) => asset.type === AssetTypeEnum.Image);
isLinkActionAvailable = isAllOwned && (isLivePhoto || isLivePhotoCandidate);
}
const handleEscape = () => {
@ -53,11 +63,14 @@
}
};
const handleLink = (asset: AssetResponseDto) => {
if (asset.livePhotoVideoId) {
assetStore.removeAssets([asset.livePhotoVideoId]);
}
assetStore.updateAssets([asset]);
const handleLink: OnLink = ({ still, motion }) => {
assetStore.removeAssets([motion.id]);
assetStore.updateAssets([still]);
};
const handleUnlink: OnUnlink = ({ still, motion }) => {
assetStore.addAssets([motion]);
assetStore.updateAssets([still]);
};
onDestroy(() => {
@ -87,8 +100,13 @@
onUnstack={(assets) => assetStore.addAssets(assets)}
/>
{/if}
{#if $selectedAssets.size === 2 && [...$selectedAssets].some((asset) => asset.type === AssetTypeEnum.Image && [...$selectedAssets].some((asset) => asset.type === AssetTypeEnum.Video))}
<LinkLivePhotoAction menuItem onLink={handleLink} />
{#if isLinkActionAvailable}
<LinkLivePhotoAction
menuItem
unlink={[...$selectedAssets].length === 1}
onLink={handleLink}
onUnlink={handleUnlink}
/>
{/if}
<ChangeDate menuItem />
<ChangeLocation menuItem />