feat(server, web): album orders (#7819)

* feat: album orders

* fix: tests

* pr feedback

* pr feedback

* pr feedback

* fix: tests

* add comment

* pr feedback

* fix: rendering issue

* wording

* fix: order value doesn't change

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
martin
2024-03-14 17:45:03 +01:00
committed by GitHub
parent 1c4637cb43
commit 31f7e1aca3
32 changed files with 251 additions and 49 deletions

View File

@@ -1,22 +1,55 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import type { AlbumResponseDto, UserResponseDto } from '@immich/sdk';
import { mdiClose, mdiPlus } from '@mdi/js';
import { updateAlbumInfo, type AlbumResponseDto, type UserResponseDto, AssetOrder } from '@immich/sdk';
import { mdiArrowDownThin, mdiArrowUpThin, mdiClose, mdiPlus } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import SettingDropdown from '../shared-components/settings/setting-dropdown.svelte';
import type { RenderedOption } from '../elements/dropdown.svelte';
import { handleError } from '$lib/utils/handle-error';
import { findKey } from 'lodash-es';
export let album: AlbumResponseDto;
export let order: AssetOrder | undefined;
export let user: UserResponseDto;
export let onChangeOrder: (order: AssetOrder) => void;
const options: Record<AssetOrder, RenderedOption> = {
[AssetOrder.Asc]: { icon: mdiArrowUpThin, title: 'Oldest first' },
[AssetOrder.Desc]: { icon: mdiArrowDownThin, title: 'Newest first' },
};
$: selectedOption = order ? options[order] : options[AssetOrder.Desc];
const dispatch = createEventDispatcher<{
close: void;
toggleEnableActivity: void;
showSelectSharedUser: void;
}>();
const handleToggle = async (returnedOption: RenderedOption) => {
if (selectedOption === returnedOption) {
return;
}
let order = AssetOrder.Desc;
order = findKey(options, (option) => option === returnedOption) as AssetOrder;
try {
await updateAlbumInfo({
id: album.id,
updateAlbumDto: {
order,
},
});
onChangeOrder(order);
} catch (error) {
handleError(error, 'Error updating album order');
}
};
</script>
<FullScreenModal onClose={() => dispatch('close')}>
@@ -34,8 +67,16 @@
<div class=" items-center justify-center p-4">
<div class="py-2">
<h2 class="text-gray text-sm mb-3">SHARING</h2>
<div class="p-2">
<h2 class="text-gray text-sm mb-2">SETTINGS</h2>
<div class="grid p-2 gap-y-2">
{#if order}
<SettingDropdown
title="Display order"
options={Object.values(options)}
selectedOption={options[order]}
onToggle={handleToggle}
/>
{/if}
<SettingSwitch
title="Comments & likes"
subtitle="Let others respond"

View File

@@ -20,7 +20,7 @@
[SlideshowNavigation.DescendingOrder]: { icon: mdiArrowDownThin, title: 'Forward' },
};
export const handleToggle = (selectedOption: RenderedOption) => {
const handleToggle = (selectedOption: RenderedOption) => {
for (const [key, option] of Object.entries(options)) {
if (option === selectedOption) {
$slideshowNavigation = key as SlideshowNavigation;

View File

@@ -161,7 +161,10 @@ export class AssetStore {
this.assetToBucket = {};
this.albumAssets = new Set();
const buckets = await getTimeBuckets({ ...this.options, key: getKey() });
const buckets = await getTimeBuckets({
...this.options,
key: getKey(),
});
this.initialized = true;

View File

@@ -58,6 +58,7 @@
updateAlbumInfo,
type ActivityResponseDto,
type UserResponseDto,
AssetOrder,
} from '@immich/sdk';
import {
mdiArrowLeft,
@@ -83,6 +84,7 @@
$: album = data.album;
$: albumId = album.id;
$: albumKey = `${albumId}_${albumOrder}`;
$: {
if (!album.isActivityEnabled && $numberOfComments === 0) {
@@ -112,8 +114,9 @@
let globalWidth: number;
let assetGridWidth: number;
let textArea: HTMLTextAreaElement;
let albumOrder: AssetOrder | undefined = data.album.order;
$: assetStore = new AssetStore({ albumId });
$: assetStore = new AssetStore({ albumId, order: albumOrder });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
@@ -512,7 +515,7 @@
style={`width:${assetGridWidth}px`}
>
<!-- Use key because AssetGrid can't deal with changing stores -->
{#key albumId}
{#key albumKey}
{#if viewMode === ViewMode.SELECT_ASSETS}
<AssetGrid
assetStore={timelineStore}
@@ -679,7 +682,9 @@
{#if viewMode === ViewMode.OPTIONS && $user}
<AlbumOptions
{album}
order={albumOrder}
user={$user}
onChangeOrder={(order) => (albumOrder = order)}
on:close={() => (viewMode = ViewMode.VIEW)}
on:toggleEnableActivity={handleToggleEnableActivity}
on:showSelectSharedUser={() => (viewMode = ViewMode.SELECT_USERS)}

View File

@@ -1,5 +1,5 @@
import { faker } from '@faker-js/faker';
import type { AlbumResponseDto } from '@immich/sdk';
import { AssetOrder, type AlbumResponseDto } from '@immich/sdk';
import { Sync } from 'factory.ts';
import { userFactory } from './user-factory';
@@ -18,4 +18,5 @@ export const albumFactory = Sync.makeFactory<AlbumResponseDto>({
sharedUsers: [],
hasSharedLink: false,
isActivityEnabled: true,
order: AssetOrder.Desc,
});