mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	feat(web): sort albums in modal (#12331)
This commit is contained in:
		
							parent
							
								
									0a8bd7dc66
								
							
						
					
					
						commit
						720412645f
					
				@ -1,6 +1,6 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { onMount } from 'svelte';
 | 
					  import { onMount } from 'svelte';
 | 
				
			||||||
  import { groupBy, orderBy } from 'lodash-es';
 | 
					  import { groupBy } from 'lodash-es';
 | 
				
			||||||
  import { addUsersToAlbum, deleteAlbum, type AlbumUserAddDto, type AlbumResponseDto, isHttpError } from '@immich/sdk';
 | 
					  import { addUsersToAlbum, deleteAlbum, type AlbumUserAddDto, type AlbumResponseDto, isHttpError } from '@immich/sdk';
 | 
				
			||||||
  import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
 | 
					  import { mdiDeleteOutline, mdiShareVariantOutline, mdiFolderDownloadOutline, mdiRenameOutline } from '@mdi/js';
 | 
				
			||||||
  import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
 | 
					  import EditAlbumForm from '$lib/components/forms/edit-album-form.svelte';
 | 
				
			||||||
@ -17,7 +17,13 @@
 | 
				
			|||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
  import { downloadAlbum } from '$lib/utils/asset-utils';
 | 
					  import { downloadAlbum } from '$lib/utils/asset-utils';
 | 
				
			||||||
  import { normalizeSearchString } from '$lib/utils/string-utils';
 | 
					  import { normalizeSearchString } from '$lib/utils/string-utils';
 | 
				
			||||||
  import { getSelectedAlbumGroupOption, type AlbumGroup, confirmAlbumDelete } from '$lib/utils/album-utils';
 | 
					  import {
 | 
				
			||||||
 | 
					    getSelectedAlbumGroupOption,
 | 
				
			||||||
 | 
					    type AlbumGroup,
 | 
				
			||||||
 | 
					    confirmAlbumDelete,
 | 
				
			||||||
 | 
					    sortAlbums,
 | 
				
			||||||
 | 
					    stringToSortOrder,
 | 
				
			||||||
 | 
					  } from '$lib/utils/album-utils';
 | 
				
			||||||
  import type { ContextMenuPosition } from '$lib/utils/context-menu';
 | 
					  import type { ContextMenuPosition } from '$lib/utils/context-menu';
 | 
				
			||||||
  import { user } from '$lib/stores/user.store';
 | 
					  import { user } from '$lib/stores/user.store';
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
@ -45,10 +51,6 @@
 | 
				
			|||||||
    [option: string]: (order: SortOrder, albums: AlbumResponseDto[]) => AlbumGroup[];
 | 
					    [option: string]: (order: SortOrder, albums: AlbumResponseDto[]) => AlbumGroup[];
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  interface AlbumSortOption {
 | 
					 | 
				
			||||||
    [option: string]: (order: SortOrder, albums: AlbumResponseDto[]) => AlbumResponseDto[];
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const groupOptions: AlbumGroupOption = {
 | 
					  const groupOptions: AlbumGroupOption = {
 | 
				
			||||||
    /** No grouping */
 | 
					    /** No grouping */
 | 
				
			||||||
    [AlbumGroupBy.None]: (order, albums): AlbumGroup[] => {
 | 
					    [AlbumGroupBy.None]: (order, albums): AlbumGroup[] => {
 | 
				
			||||||
@ -116,41 +118,6 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sortOptions: AlbumSortOption = {
 | 
					 | 
				
			||||||
    /** Sort by album title */
 | 
					 | 
				
			||||||
    [AlbumSortBy.Title]: (order, albums) => {
 | 
					 | 
				
			||||||
      const sortSign = order === SortOrder.Desc ? -1 : 1;
 | 
					 | 
				
			||||||
      return albums.slice().sort((a, b) => a.albumName.localeCompare(b.albumName, $locale) * sortSign);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Sort by asset count */
 | 
					 | 
				
			||||||
    [AlbumSortBy.ItemCount]: (order, albums) => {
 | 
					 | 
				
			||||||
      return orderBy(albums, 'assetCount', [order]);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Sort by last modified */
 | 
					 | 
				
			||||||
    [AlbumSortBy.DateModified]: (order, albums) => {
 | 
					 | 
				
			||||||
      return orderBy(albums, [({ updatedAt }) => new Date(updatedAt)], [order]);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Sort by creation date */
 | 
					 | 
				
			||||||
    [AlbumSortBy.DateCreated]: (order, albums) => {
 | 
					 | 
				
			||||||
      return orderBy(albums, [({ createdAt }) => new Date(createdAt)], [order]);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Sort by the most recent photo date */
 | 
					 | 
				
			||||||
    [AlbumSortBy.MostRecentPhoto]: (order, albums) => {
 | 
					 | 
				
			||||||
      albums = orderBy(albums, [({ endDate }) => (endDate ? new Date(endDate) : '')], [order]);
 | 
					 | 
				
			||||||
      return albums.sort(sortUnknownYearAlbums);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Sort by the oldest photo date */
 | 
					 | 
				
			||||||
    [AlbumSortBy.OldestPhoto]: (order, albums) => {
 | 
					 | 
				
			||||||
      albums = orderBy(albums, [({ startDate }) => (startDate ? new Date(startDate) : '')], [order]);
 | 
					 | 
				
			||||||
      return albums.sort(sortUnknownYearAlbums);
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let albums: AlbumResponseDto[] = [];
 | 
					  let albums: AlbumResponseDto[] = [];
 | 
				
			||||||
  let filteredAlbums: AlbumResponseDto[] = [];
 | 
					  let filteredAlbums: AlbumResponseDto[] = [];
 | 
				
			||||||
  let groupedAlbums: AlbumGroup[] = [];
 | 
					  let groupedAlbums: AlbumGroup[] = [];
 | 
				
			||||||
@ -208,16 +175,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // Step 4: Sort albums amongst each group.
 | 
					  // Step 4: Sort albums amongst each group.
 | 
				
			||||||
  $: {
 | 
					  $: {
 | 
				
			||||||
    const defaultSortOption = AlbumSortBy.DateModified;
 | 
					 | 
				
			||||||
    const selectedSortOption = userSettings.sortBy ?? defaultSortOption;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const sortFunc = sortOptions[selectedSortOption] ?? sortOptions[defaultSortOption];
 | 
					 | 
				
			||||||
    const sortOrder = stringToSortOrder(userSettings.sortOrder);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    groupedAlbums = groupedAlbums.map((group) => ({
 | 
					    groupedAlbums = groupedAlbums.map((group) => ({
 | 
				
			||||||
      id: group.id,
 | 
					      id: group.id,
 | 
				
			||||||
      name: group.name,
 | 
					      name: group.name,
 | 
				
			||||||
      albums: sortFunc(sortOrder, group.albums),
 | 
					      albums: sortAlbums(group.albums, { sortBy: userSettings.sortBy, orderBy: userSettings.sortOrder }),
 | 
				
			||||||
    }));
 | 
					    }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    albumGroupIds = groupedAlbums.map(({ id }) => id);
 | 
					    albumGroupIds = groupedAlbums.map(({ id }) => id);
 | 
				
			||||||
@ -231,20 +192,6 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sortUnknownYearAlbums = (a: AlbumResponseDto, b: AlbumResponseDto) => {
 | 
					 | 
				
			||||||
    if (!a.endDate) {
 | 
					 | 
				
			||||||
      return 1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!b.endDate) {
 | 
					 | 
				
			||||||
      return -1;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const stringToSortOrder = (order: string) => {
 | 
					 | 
				
			||||||
    return order === 'desc' ? SortOrder.Desc : SortOrder.Asc;
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const showAlbumContextMenu = (contextMenuDetail: ContextMenuPosition, album: AlbumResponseDto) => {
 | 
					  const showAlbumContextMenu = (contextMenuDetail: ContextMenuPosition, album: AlbumResponseDto) => {
 | 
				
			||||||
    contextMenuTargetAlbum = album;
 | 
					    contextMenuTargetAlbum = album;
 | 
				
			||||||
    contextMenuPosition = {
 | 
					    contextMenuPosition = {
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,8 @@
 | 
				
			|||||||
  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
 | 
					  import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
 | 
				
			||||||
  import { initInput } from '$lib/actions/focus';
 | 
					  import { initInput } from '$lib/actions/focus';
 | 
				
			||||||
  import { t } from 'svelte-i18n';
 | 
					  import { t } from 'svelte-i18n';
 | 
				
			||||||
 | 
					  import { sortAlbums } from '$lib/utils/album-utils';
 | 
				
			||||||
 | 
					  import { albumViewSettings } from '$lib/stores/preferences.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let albums: AlbumResponseDto[] = [];
 | 
					  let albums: AlbumResponseDto[] = [];
 | 
				
			||||||
  let recentAlbums: AlbumResponseDto[] = [];
 | 
					  let recentAlbums: AlbumResponseDto[] = [];
 | 
				
			||||||
@ -29,14 +31,14 @@
 | 
				
			|||||||
    loading = false;
 | 
					    loading = false;
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: {
 | 
					  $: filteredAlbums = sortAlbums(
 | 
				
			||||||
    filteredAlbums =
 | 
					 | 
				
			||||||
    search.length > 0 && albums.length > 0
 | 
					    search.length > 0 && albums.length > 0
 | 
				
			||||||
      ? albums.filter((album) => {
 | 
					      ? albums.filter((album) => {
 | 
				
			||||||
          return normalizeSearchString(album.albumName).includes(normalizeSearchString(search));
 | 
					          return normalizeSearchString(album.albumName).includes(normalizeSearchString(search));
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        : albums;
 | 
					      : albums,
 | 
				
			||||||
  }
 | 
					    { sortBy: $albumViewSettings.sortBy, orderBy: $albumViewSettings.sortOrder },
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleSelect = (album: AlbumResponseDto) => {
 | 
					  const handleSelect = (album: AlbumResponseDto) => {
 | 
				
			||||||
    dispatch('album', album);
 | 
					    dispatch('album', album);
 | 
				
			||||||
 | 
				
			|||||||
@ -7,11 +7,13 @@ import {
 | 
				
			|||||||
  AlbumSortBy,
 | 
					  AlbumSortBy,
 | 
				
			||||||
  SortOrder,
 | 
					  SortOrder,
 | 
				
			||||||
  albumViewSettings,
 | 
					  albumViewSettings,
 | 
				
			||||||
 | 
					  locale,
 | 
				
			||||||
  type AlbumViewSettings,
 | 
					  type AlbumViewSettings,
 | 
				
			||||||
} from '$lib/stores/preferences.store';
 | 
					} from '$lib/stores/preferences.store';
 | 
				
			||||||
import { handleError } from '$lib/utils/handle-error';
 | 
					import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
import type { AlbumResponseDto } from '@immich/sdk';
 | 
					import type { AlbumResponseDto } from '@immich/sdk';
 | 
				
			||||||
import * as sdk from '@immich/sdk';
 | 
					import * as sdk from '@immich/sdk';
 | 
				
			||||||
 | 
					import { orderBy } from 'lodash-es';
 | 
				
			||||||
import { t } from 'svelte-i18n';
 | 
					import { t } from 'svelte-i18n';
 | 
				
			||||||
import { get } from 'svelte/store';
 | 
					import { get } from 'svelte/store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -213,3 +215,63 @@ export const confirmAlbumDelete = async (album: AlbumResponseDto) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return dialogController.show({ prompt });
 | 
					  return dialogController.show({ prompt });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface AlbumSortOption {
 | 
				
			||||||
 | 
					  [option: string]: (order: SortOrder, albums: AlbumResponseDto[]) => AlbumResponseDto[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sortUnknownYearAlbums = (a: AlbumResponseDto, b: AlbumResponseDto) => {
 | 
				
			||||||
 | 
					  if (!a.endDate) {
 | 
				
			||||||
 | 
					    return 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!b.endDate) {
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const stringToSortOrder = (order: string) => {
 | 
				
			||||||
 | 
					  return order === 'desc' ? SortOrder.Desc : SortOrder.Asc;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sortOptions: AlbumSortOption = {
 | 
				
			||||||
 | 
					  /** Sort by album title */
 | 
				
			||||||
 | 
					  [AlbumSortBy.Title]: (order, albums) => {
 | 
				
			||||||
 | 
					    const sortSign = order === SortOrder.Desc ? -1 : 1;
 | 
				
			||||||
 | 
					    return albums.slice().sort((a, b) => a.albumName.localeCompare(b.albumName, get(locale)) * sortSign);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sort by asset count */
 | 
				
			||||||
 | 
					  [AlbumSortBy.ItemCount]: (order, albums) => {
 | 
				
			||||||
 | 
					    return orderBy(albums, 'assetCount', [order]);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sort by last modified */
 | 
				
			||||||
 | 
					  [AlbumSortBy.DateModified]: (order, albums) => {
 | 
				
			||||||
 | 
					    return orderBy(albums, [({ updatedAt }) => new Date(updatedAt)], [order]);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sort by creation date */
 | 
				
			||||||
 | 
					  [AlbumSortBy.DateCreated]: (order, albums) => {
 | 
				
			||||||
 | 
					    return orderBy(albums, [({ createdAt }) => new Date(createdAt)], [order]);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sort by the most recent photo date */
 | 
				
			||||||
 | 
					  [AlbumSortBy.MostRecentPhoto]: (order, albums) => {
 | 
				
			||||||
 | 
					    albums = orderBy(albums, [({ endDate }) => (endDate ? new Date(endDate) : '')], [order]);
 | 
				
			||||||
 | 
					    return albums.sort(sortUnknownYearAlbums);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Sort by the oldest photo date */
 | 
				
			||||||
 | 
					  [AlbumSortBy.OldestPhoto]: (order, albums) => {
 | 
				
			||||||
 | 
					    albums = orderBy(albums, [({ startDate }) => (startDate ? new Date(startDate) : '')], [order]);
 | 
				
			||||||
 | 
					    return albums.sort(sortUnknownYearAlbums);
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sortAlbums = (albums: AlbumResponseDto[], { sortBy, orderBy }: { sortBy: string; orderBy: string }) => {
 | 
				
			||||||
 | 
					  const sort = sortOptions[sortBy] ?? sortOptions[AlbumSortBy.DateModified];
 | 
				
			||||||
 | 
					  const order = stringToSortOrder(orderBy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return sort(order, albums);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user