feat(web): add current view asset to album (#923)

This commit is contained in:
Jason Rasmussen 2022-11-04 10:32:09 -04:00 committed by GitHub
parent d696ce4e41
commit 5aa06ed3be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 225 additions and 4 deletions

View File

@ -0,0 +1,39 @@
<script lang="ts">
import { AlbumResponseDto, ThumbnailFormat } from '@api';
import { createEventDispatcher } from 'svelte';
const dispatcher = createEventDispatcher();
export let album: AlbumResponseDto;
export let variant: 'simple' | 'full' = 'full';
</script>
<button
on:click={() => dispatcher('album')}
class="flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
>
<div class="h-12 w-12">
<img
src={`/api/asset/thumbnail/${album.albumThumbnailAssetId}?format=${ThumbnailFormat.Webp}`}
alt={album.albumName}
class={`object-cover h-full w-full transition-all z-0 rounded-xl duration-300 hover:shadow-lg`}
data-testid="album-image"
/>
</div>
<div class="h-12 flex flex-col items-start justify-center">
<span>{album.albumName}</span>
<span class="flex gap-1 text-sm">
{#if variant === 'simple'}
<span
>{#if album.shared}Shared{/if}
</span>
{:else}
<span>{album.assetCount} items</span>
<span> · {new Date(album.createdAt).toLocaleDateString()}</span>
<span
>{#if album.shared} · Shared{/if}
</span>
{/if}
</span>
</div>
</button>

View File

@ -0,0 +1,95 @@
<script lang="ts">
import { AlbumResponseDto, api } from '@api';
import { createEventDispatcher, onMount } from 'svelte';
import Plus from 'svelte-material-icons/Plus.svelte';
import BaseModal from '../shared-components/base-modal.svelte';
import AlbumListItem from './album-list-item.svelte';
let albums: AlbumResponseDto[] = [];
let recentAlbums: AlbumResponseDto[] = [];
let loading = true;
const dispatch = createEventDispatcher();
export let shared: boolean;
onMount(async () => {
const { data } = await api.albumApi.getAllAlbums();
albums = data;
recentAlbums = albums
.filter((album) => album.shared === shared)
.sort((a, b) => (new Date(a.createdAt) > new Date(b.createdAt) ? -1 : 1))
.slice(0, 3);
loading = false;
});
const handleSelect = (album: AlbumResponseDto) => {
dispatch('album', { album });
};
const handleNew = () => {
if (shared) {
dispatch('newAlbum');
} else {
dispatch('newSharedAlbum');
}
};
</script>
<BaseModal on:close={() => dispatch('close')}>
<svelte:fragment slot="title">
<span class="flex gap-2 place-items-center">
<p class="font-medium">
Add to {#if shared}shared {/if}
</p>
</span>
</svelte:fragment>
<div class=" max-h-[400px] overflow-y-auto immich-scrollbar">
<div class="flex flex-col mb-2">
{#if loading}
{#each { length: 3 } as _}
<div class="animate-pulse flex gap-4 px-6 py-2">
<div class="h-12 w-12 bg-slate-200 rounded-xl" />
<div class="flex flex-col items-start justify-center gap-2">
<span class="animate-pulse w-36 h-4 bg-slate-200" />
<div class="flex animate-pulse gap-1">
<span class="w-8 h-3 bg-slate-200" />
<span class="w-20 h-3 bg-slate-200" />
</div>
</div>
</div>
{/each}
{:else}
<button
on:click={handleNew}
class="flex gap-4 px-6 py-2 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors items-center"
>
<div class="h-12 w-12 flex justify-center items-center">
<Plus size="30" />
</div>
<p class="">
New {#if shared}Shared {/if}Album
</p>
</button>
{#if albums.length > 0}
<p class="text-sm font-medium px-5 py-1">RECENT</p>
{#each recentAlbums as album}
{#key album.id}
<AlbumListItem variant="simple" {album} on:album={() => handleSelect(album)} />
{/key}
{/each}
<p class="text-sm font-medium px-5 py-1">ALL ALBUMS</p>
{#each albums as album}
{#key album.id}
<AlbumListItem {album} on:album={() => handleSelect(album)} />
{/key}
{/each}
{:else}
<p class="text-sm px-5 py-1">It looks like you do not have any albums yet.</p>
{/if}
{/if}
</div>
</div>
</BaseModal>

View File

@ -4,9 +4,30 @@
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
import InformationOutline from 'svelte-material-icons/InformationOutline.svelte';
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
const dispatch = createEventDispatcher();
let contextMenuPosition = { x: 0, y: 0 };
let isShowAssetOptions = false;
const showOptionsMenu = (event: CustomEvent) => {
contextMenuPosition = {
x: event.detail.mouseEvent.x,
y: event.detail.mouseEvent.y
};
isShowAssetOptions = !isShowAssetOptions;
};
const onMenuClick = (eventName: string) => {
isShowAssetOptions = false;
dispatch(eventName);
};
</script>
<div
@ -19,5 +40,15 @@
<CircleIconButton logo={CloudDownloadOutline} on:click={() => dispatch('download')} />
<CircleIconButton logo={DeleteOutline} on:click={() => dispatch('delete')} />
<CircleIconButton logo={InformationOutline} on:click={() => dispatch('showDetail')} />
<CircleIconButton logo={DotsVertical} on:click={(event) => showOptionsMenu(event)} />
</div>
</div>
{#if isShowAssetOptions}
<ContextMenu {...contextMenuPosition} on:clickoutside={() => (isShowAssetOptions = false)}>
<div class="flex flex-col rounded-lg ">
<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
<MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" />
</div>
</ContextMenu>
{/if}

View File

@ -6,9 +6,17 @@
import ChevronLeft from 'svelte-material-icons/ChevronLeft.svelte';
import PhotoViewer from './photo-viewer.svelte';
import DetailPanel from './detail-panel.svelte';
import { goto } from '$app/navigation';
import { downloadAssets } from '$lib/stores/download';
import VideoViewer from './video-viewer.svelte';
import { api, AssetResponseDto, AssetTypeEnum, AlbumResponseDto } from '@api';
import AlbumSelectionModal from './album-selection-modal.svelte';
import {
api,
AddAssetsResponseDto,
AssetResponseDto,
AssetTypeEnum,
AlbumResponseDto
} from '@api';
import {
notificationController,
NotificationType
@ -29,6 +37,8 @@
let halfRightHover = false;
let isShowDetail = false;
let appearsInAlbums: AlbumResponseDto[] = [];
let isShowAlbumPicker = false;
let addToSharedAlbum = true;
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key);
@ -167,6 +177,39 @@
console.error('Error deleteSelectedAssetHandler', e);
}
};
const openAlbumPicker = (shared: boolean) => {
isShowAlbumPicker = true;
addToSharedAlbum = shared;
};
const showAddNotification = (dto: AddAssetsResponseDto) => {
notificationController.show({
message: `Added ${dto.successfullyAdded} to ${dto.album?.albumName}`,
type: NotificationType.Info
});
if (dto.successfullyAdded === 1 && dto.album) {
appearsInAlbums = [...appearsInAlbums, dto.album];
}
};
const handleAddToNewAlbum = () => {
isShowAlbumPicker = false;
api.albumApi.createAlbum({ albumName: 'Untitled', assetIds: [asset.id] }).then((response) => {
const album = response.data;
goto('/albums/' + album.id);
});
};
const handleAddToAlbum = async (event: CustomEvent<{ album: AlbumResponseDto }>) => {
isShowAlbumPicker = false;
const album = event.detail.album;
api.albumApi
.addAssetsToAlbum(album.id, { assetIds: [asset.id] })
.then((response) => showAddNotification(response.data));
};
</script>
<section
@ -179,6 +222,8 @@
on:showDetail={showDetailInfoHandler}
on:download={downloadFile}
on:delete={deleteAsset}
on:addToAlbum={() => openAlbumPicker(false)}
on:addToSharedAlbum={() => openAlbumPicker(true)}
/>
</div>
@ -246,6 +291,17 @@
<DetailPanel {asset} albums={appearsInAlbums} on:close={() => (isShowDetail = false)} />
</div>
{/if}
{#if isShowAlbumPicker}
<AlbumSelectionModal
shared={addToSharedAlbum}
on:newAlbum={handleAddToNewAlbum}
on:newSharedAlbum={handleAddToNewAlbum}
on:album={handleAddToAlbum}
on:close={() => (isShowAlbumPicker = false)}
/>
<div class="w-full h-full">Hello</div>
{/if}
</section>
<style>

View File

@ -38,7 +38,7 @@
on:out-click={() => dispatch('close')}
class="bg-immich-bg dark:bg-immich-dark-gray dark:text-immich-dark-fg w-[450px] min-h-[200px] max-h-[500px] rounded-lg shadow-md"
>
<div class="flex justify-between place-items-center p-5">
<div class="flex justify-between place-items-center px-5 py-3">
<div>
<slot name="title">
<p>Modal Title</p>

View File

@ -32,7 +32,7 @@
<div
transition:slide={{ duration: 200, easing: quintOut }}
bind:this={menuEl}
class="absolute w-[175px] z-[99999] rounded-lg shadow-md"
class="absolute w-[200px] z-[99999] rounded-lg overflow-hidden"
style={`top: ${y}px; left: ${x}px;`}
use:clickOutside
on:out-click={() => dispatch('clickoutside')}

View File

@ -16,7 +16,7 @@
<button
class:disabled={isDisabled}
on:click={handleClick}
class="bg-white hover:bg-gray-300 dark:text-immich-dark-bg transition-all p-4 w-full text-left rounded-lg text-sm"
class="bg-white hover:bg-gray-300 dark:text-immich-dark-bg transition-all p-4 w-full text-left text-sm"
>
{#if text}
{text}