1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-26 10:50:29 +02:00

fix(web): re-render albums (#7403)

* fix: re-render albums

* fix: album description

* fix: reactivity

* fix album reactivity + components for title and description

* only update AssetGrid when albumId changes

* remove title and description bindings

* remove console.log

* chore: fix merge

* pr feedback

* pr feedback

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
This commit is contained in:
martin 2024-02-28 22:39:53 +01:00 committed by GitHub
parent e4f32a045d
commit 84fe41df31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 209 additions and 179 deletions

View File

@ -0,0 +1,45 @@
<script lang="ts">
import { autoGrowHeight } from '$lib/utils/autogrow';
import { updateAlbumInfo } from '@immich/sdk';
import { handleError } from '$lib/utils/handle-error';
export let id: string;
export let description: string;
export let isOwned: boolean;
$: newDescription = description;
const handleUpdateDescription = async () => {
if (newDescription === description) {
return;
}
try {
await updateAlbumInfo({
id,
updateAlbumDto: {
description: newDescription,
},
});
} catch (error) {
handleError(error, 'Error updating album description');
return;
}
description = newDescription;
};
</script>
{#if isOwned}
<textarea
class="w-full mt-2 resize-none overflow-hidden text-black dark:text-white border-b-2 border-transparent border-gray-500 bg-transparent text-base outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:focus:border-immich-dark-primary hover:border-gray-400"
bind:value={newDescription}
on:input={(e) => autoGrowHeight(e.currentTarget)}
on:focusout={handleUpdateDescription}
use:autoGrowHeight
placeholder="Add description"
/>
{:else if description}
<p class="break-words whitespace-pre-line w-full text-black dark:text-white text-base">
{description}
</p>
{/if}

View File

@ -0,0 +1,42 @@
<script lang="ts">
import { updateAlbumInfo } from '@immich/sdk';
import { handleError } from '$lib/utils/handle-error';
export let id: string;
export let albumName: string;
export let isOwned: boolean;
$: newAlbumName = albumName;
const handleUpdateName = async () => {
if (newAlbumName === albumName) {
return;
}
try {
await updateAlbumInfo({
id,
updateAlbumDto: {
albumName: newAlbumName,
},
});
} catch (error) {
handleError(error, 'Unable to update album name');
return;
}
albumName = newAlbumName;
};
</script>
<input
on:keydown={(e) => e.key === 'Enter' && e.currentTarget.blur()}
on:blur={handleUpdateName}
class="w-[99%] mb-2 border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
? 'hover:border-gray-400'
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
type="text"
bind:value={newAlbumName}
disabled={!isOwned}
title="Edit Title"
placeholder="Add a title"
/>

View File

@ -11,7 +11,7 @@
export let isEdited = false; export let isEdited = false;
const dispatch = createEventDispatcher<{ toggle: boolean }>(); const dispatch = createEventDispatcher<{ toggle: boolean }>();
const onToggle = (event: Event) => dispatch('toggle', (event.target as HTMLInputElement).checked); const onToggle = (ischecked: boolean) => dispatch('toggle', ischecked);
</script> </script>
<div class="flex place-items-center justify-between"> <div class="flex place-items-center justify-between">
@ -34,5 +34,5 @@
<slot /> <slot />
</div> </div>
<Slider bind:checked {disabled} on:click={onToggle} /> <Slider bind:checked {disabled} on:toggle={({ detail }) => onToggle(detail)} />
</div> </div>

View File

@ -41,7 +41,6 @@
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { downloadArchive } from '$lib/utils/asset-utils'; import { downloadArchive } from '$lib/utils/asset-utils';
import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
import { getContextMenuPosition } from '$lib/utils/context-menu'; import { getContextMenuPosition } from '$lib/utils/context-menu';
import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { openFileUploadDialog } from '$lib/utils/file-uploader';
@ -71,17 +70,19 @@
mdiPlus, mdiPlus,
mdiShareVariantOutline, mdiShareVariantOutline,
} from '@mdi/js'; } from '@mdi/js';
import { onMount } from 'svelte';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import type { PageData } from './$types'; import type { PageData } from './$types';
import AlbumTitle from '$lib/components/album-page/album-title.svelte';
import AlbumDescription from '$lib/components/album-page/album-description.svelte';
import { handlePromiseError } from '$lib/utils';
export let data: PageData; export let data: PageData;
let { isViewing: showAssetViewer, setAssetId } = assetViewingStore; let { isViewing: showAssetViewer, setAssetId } = assetViewingStore;
let { slideshowState, slideshowShuffle } = slideshowStore; let { slideshowState, slideshowShuffle } = slideshowStore;
let album = data.album; $: album = data.album;
let description = album.description; $: albumId = album.id;
$: { $: {
if (!album.isActivityEnabled && $numberOfComments === 0) { if (!album.isActivityEnabled && $numberOfComments === 0) {
@ -103,9 +104,7 @@
let backUrl: string = AppRoute.ALBUMS; let backUrl: string = AppRoute.ALBUMS;
let viewMode = ViewMode.VIEW; let viewMode = ViewMode.VIEW;
let titleInput: HTMLInputElement;
let isCreatingSharedAlbum = false; let isCreatingSharedAlbum = false;
let currentAlbumName = album.albumName;
let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 }; let contextMenuPosition: { x: number; y: number } = { x: 0, y: 0 };
let isShowActivity = false; let isShowActivity = false;
let isLiked: ActivityResponseDto | null = null; let isLiked: ActivityResponseDto | null = null;
@ -114,11 +113,11 @@
let assetGridWidth: number; let assetGridWidth: number;
let textArea: HTMLTextAreaElement; let textArea: HTMLTextAreaElement;
const assetStore = new AssetStore({ albumId: album.id }); $: assetStore = new AssetStore({ albumId });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
const timelineStore = new AssetStore({ isArchived: false }, album.id); $: timelineStore = new AssetStore({ isArchived: false }, albumId);
const timelineInteractionStore = createAssetInteractionStore(); const timelineInteractionStore = createAssetInteractionStore();
const { selectedAssets: timelineSelected } = timelineInteractionStore; const { selectedAssets: timelineSelected } = timelineInteractionStore;
@ -132,7 +131,7 @@
$: showActivityStatus = $: showActivityStatus =
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0); album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
$: afterNavigate(({ from }) => { afterNavigate(({ from }) => {
assetViewingStore.showAssetViewer(false); assetViewingStore.showAssetViewer(false);
let url: string | undefined = from?.url?.pathname; let url: string | undefined = from?.url?.pathname;
@ -218,11 +217,10 @@
isShowActivity = !isShowActivity; isShowActivity = !isShowActivity;
}; };
onMount(async () => { $: if (album.sharedUsers.length > 0) {
if (album.sharedUsers.length > 0) { handlePromiseError(getFavorite());
await Promise.all([getFavorite(), getNumberOfComments()]); handlePromiseError(getNumberOfComments());
} }
});
const handleKeypress = (event: KeyboardEvent) => { const handleKeypress = (event: KeyboardEvent) => {
if (event.target !== textArea) { if (event.target !== textArea) {
@ -416,42 +414,6 @@
handleError(error, 'Unable to update album cover'); handleError(error, 'Unable to update album cover');
} }
}; };
const handleUpdateName = async () => {
if (currentAlbumName === album.albumName) {
return;
}
try {
await updateAlbumInfo({
id: album.id,
updateAlbumDto: {
albumName: album.albumName,
},
});
currentAlbumName = album.albumName;
} catch (error) {
handleError(error, 'Unable to update album name');
}
};
const handleUpdateDescription = async () => {
if (album.description === description) {
return;
}
try {
await updateAlbumInfo({
id: album.id,
updateAlbumDto: {
description,
},
});
album.description = description;
} catch (error) {
handleError(error, 'Error updating album description');
}
};
</script> </script>
<svelte:window on:keydown={handleKeypress} /> <svelte:window on:keydown={handleKeypress} />
@ -576,134 +538,115 @@
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg" class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg"
style={`width:${assetGridWidth}px`} style={`width:${assetGridWidth}px`}
> >
{#if viewMode === ViewMode.SELECT_ASSETS} <!-- Use key because AssetGrid can't deal with changing stores -->
<AssetGrid assetStore={timelineStore} assetInteractionStore={timelineInteractionStore} isSelectionMode={true} /> {#key albumId}
{:else} {#if viewMode === ViewMode.SELECT_ASSETS}
<AssetGrid <AssetGrid
{album} assetStore={timelineStore}
{assetStore} assetInteractionStore={timelineInteractionStore}
{assetInteractionStore} isSelectionMode={true}
isShared={album.sharedUsers.length > 0}
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
showArchiveIcon
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
on:escape={handleEscape}
>
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
<!-- ALBUM TITLE -->
<section class="pt-24">
<input
on:keydown={(e) => e.key === 'Enter' && titleInput.blur()}
on:blur={handleUpdateName}
class="w-[99%] mb-2 border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
? 'hover:border-gray-400'
: 'hover:border-transparent'} bg-immich-bg focus:border-b-2 focus:border-immich-primary focus:outline-none dark:bg-immich-dark-bg dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray"
type="text"
bind:value={album.albumName}
disabled={!isOwned}
bind:this={titleInput}
title="Edit Title"
placeholder="Add a title"
/>
<!-- ALBUM SUMMARY -->
{#if album.assetCount > 0}
<span class="my-2 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
<p class="">{getDateRange()}</p>
<p>·</p>
<p>{album.assetCount} items</p>
</span>
{/if}
<!-- ALBUM SHARING -->
{#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)}
<div class="my-3 flex gap-x-1">
<!-- link -->
{#if album.hasSharedLink && isOwned}
<CircleIconButton
backgroundColor="#d3d3d3"
forceDark
size="20"
icon={mdiLink}
on:click={() => (viewMode = ViewMode.LINK_SHARING)}
/>
{/if}
<!-- owner -->
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
<UserAvatar user={album.owner} size="md" />
</button>
<!-- users -->
{#each album.sharedUsers as user (user.id)}
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
<UserAvatar {user} size="md" />
</button>
{/each}
{#if isOwned}
<CircleIconButton
backgroundColor="#d3d3d3"
forceDark
size="20"
icon={mdiPlus}
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
title="Add more users"
/>
{/if}
</div>
{/if}
<!-- ALBUM DESCRIPTION -->
{#if isOwned}
<textarea
class="w-full mt-2 resize-none overflow-hidden text-black dark:text-white border-b-2 border-transparent border-gray-500 bg-transparent text-base outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:focus:border-immich-dark-primary hover:border-gray-400"
bind:this={textArea}
bind:value={description}
on:input={() => autoGrowHeight(textArea)}
on:focusout={handleUpdateDescription}
use:autoGrowHeight
placeholder="Add description"
/>
{:else if description}
<p class="break-words whitespace-pre-line w-full text-black dark:text-white text-base">
{description}
</p>
{/if}
</section>
{/if}
{#if album.assetCount === 0}
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
<div class="w-[300px]">
<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
<button
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
class="mt-5 flex w-full place-items-center gap-6 rounded-md border bg-immich-bg px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none dark:bg-immich-dark-gray dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
>
<span class="text-text-immich-primary dark:text-immich-dark-primary"
><Icon path={mdiPlus} size="24" />
</span>
<span class="text-lg">Select photos</span>
</button>
</div>
</section>
{/if}
</AssetGrid>
{/if}
{#if showActivityStatus}
<div class="absolute z-[2] bottom-0 right-0 mb-6 mr-6 justify-self-end">
<ActivityStatus
disabled={!album.isActivityEnabled}
{isLiked}
numberOfComments={$numberOfComments}
{isShowActivity}
on:favorite={handleFavorite}
on:openActivityTab={handleOpenAndCloseActivityTab}
/> />
</div> {:else}
{/if} <AssetGrid
{album}
{assetStore}
{assetInteractionStore}
isShared={album.sharedUsers.length > 0}
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
showArchiveIcon
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
on:escape={handleEscape}
>
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
<!-- ALBUM TITLE -->
<section class="pt-24">
<AlbumTitle id={album.id} albumName={album.albumName} {isOwned} />
<!-- ALBUM SUMMARY -->
{#if album.assetCount > 0}
<span class="my-2 flex gap-2 text-sm font-medium text-gray-500" data-testid="album-details">
<p class="">{getDateRange()}</p>
<p>·</p>
<p>{album.assetCount} items</p>
</span>
{/if}
<!-- ALBUM SHARING -->
{#if album.sharedUsers.length > 0 || (album.hasSharedLink && isOwned)}
<div class="my-3 flex gap-x-1">
<!-- link -->
{#if album.hasSharedLink && isOwned}
<CircleIconButton
backgroundColor="#d3d3d3"
forceDark
size="20"
icon={mdiLink}
on:click={() => (viewMode = ViewMode.LINK_SHARING)}
/>
{/if}
<!-- owner -->
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
<UserAvatar user={album.owner} size="md" />
</button>
<!-- users -->
{#each album.sharedUsers as user (user.id)}
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
<UserAvatar {user} size="md" />
</button>
{/each}
{#if isOwned}
<CircleIconButton
backgroundColor="#d3d3d3"
forceDark
size="20"
icon={mdiPlus}
on:click={() => (viewMode = ViewMode.SELECT_USERS)}
title="Add more users"
/>
{/if}
</div>
{/if}
<!-- ALBUM DESCRIPTION -->
<AlbumDescription id={album.id} description={album.description} {isOwned} />
</section>
{/if}
{#if album.assetCount === 0}
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
<div class="w-[300px]">
<p class="text-xs dark:text-immich-dark-fg">ADD PHOTOS</p>
<button
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
class="mt-5 flex w-full place-items-center gap-6 rounded-md border bg-immich-bg px-8 py-8 text-immich-fg transition-all hover:bg-gray-100 hover:text-immich-primary dark:border-none dark:bg-immich-dark-gray dark:text-immich-dark-fg dark:hover:text-immich-dark-primary"
>
<span class="text-text-immich-primary dark:text-immich-dark-primary"
><Icon path={mdiPlus} size="24" />
</span>
<span class="text-lg">Select photos</span>
</button>
</div>
</section>
{/if}
</AssetGrid>
{/if}
{#if showActivityStatus}
<div class="absolute z-[2] bottom-0 right-0 mb-6 mr-6 justify-self-end">
<ActivityStatus
disabled={!album.isActivityEnabled}
{isLiked}
numberOfComments={$numberOfComments}
{isShowActivity}
on:favorite={handleFavorite}
on:openActivityTab={handleOpenAndCloseActivityTab}
/>
</div>
{/if}
{/key}
</main> </main>
</div> </div>
{#if album.sharedUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer} {#if album.sharedUsers.length > 0 && album && isShowActivity && $user && !$showAssetViewer}