Files
immich/web/src/lib/components/shared-components/album-selection/album-selection-utils.ts
xCJPECKOVERx 9ff664ed36 feat(web): Add to Multiple Albums (#20072)
* Multi add to album picker:
- update modal for multi select
- Update add-to-album and add-to-album-action to work with new array return from AlbumPickerModal
- Add asset-utils.addAssetsToAlbums (incomplete)

* initial addToAlbums endpoint

* - fix endpoint
- add test

* - update return type
- make open-api

* - simplify return dto
- handle notification

* - fix returns
- clean up

* - update i18n
- format & check

* - checks

* - correct successId count
- fix assets_cannot_be_added language call

* tests

* foromat

* refactor

* - update successful add message to included total attempted

* - fix web test
- format i18n

* - fix open-api

* - fix imports to resolve checks

* - PR suggestions

* open-api

* refactor addAssetsToAlbums

* refactor it again

* - fix error returns and tests

* - swap icon for IconButton
- don't nest the buttons

* open-api

* - Cleanup multi-select button to match Thumbnail

* merge and openapi

* - remove onclick from icon element

* - fix double onClose call with keyboard shortcuts

* - spelling and formatting
- apply new api permission

* - open-api

* chore: styling

* translation

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2025-08-19 00:42:47 +00:00

99 lines
3.2 KiB
TypeScript

import { sortAlbums } from '$lib/utils/album-utils';
import { normalizeSearchString } from '$lib/utils/string-utils';
import type { AlbumResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { get } from 'svelte/store';
export const SCROLL_PROPERTIES: ScrollIntoViewOptions = { block: 'center', behavior: 'smooth' };
export enum AlbumModalRowType {
SECTION = 'section',
MESSAGE = 'message',
NEW_ALBUM = 'newAlbum',
ALBUM_ITEM = 'albumItem',
}
export type AlbumModalRow = {
type: AlbumModalRowType;
selected?: boolean;
multiSelected?: boolean;
text?: string;
album?: AlbumResponseDto;
};
export const isSelectableRowType = (type: AlbumModalRowType) =>
type === AlbumModalRowType.NEW_ALBUM || type === AlbumModalRowType.ALBUM_ITEM;
const $t = get(t);
export class AlbumModalRowConverter {
private readonly shared: boolean;
private readonly sortBy: string;
private readonly orderBy: string;
constructor(shared: boolean, sortBy: string, orderBy: string) {
this.shared = shared;
this.sortBy = sortBy;
this.orderBy = orderBy;
}
toModalRows(
search: string,
recentAlbums: AlbumResponseDto[],
albums: AlbumResponseDto[],
selectedRowIndex: number,
multiSelectedAlbumIds: string[],
): AlbumModalRow[] {
// only show recent albums if no search was entered, or we're in the normal albums (non-shared) modal.
const recentAlbumsToShow = !this.shared && search.length === 0 ? recentAlbums : [];
const rows: AlbumModalRow[] = [];
rows.push({ type: AlbumModalRowType.NEW_ALBUM, selected: selectedRowIndex === 0 });
const filteredAlbums = sortAlbums(
search.length > 0 && albums.length > 0
? albums.filter((album) => {
return normalizeSearchString(album.albumName).includes(normalizeSearchString(search));
})
: albums,
{ sortBy: this.sortBy, orderBy: this.orderBy },
);
if (filteredAlbums.length > 0) {
if (recentAlbumsToShow.length > 0) {
rows.push({ type: AlbumModalRowType.SECTION, text: $t('recent').toUpperCase() });
const selectedOffsetDueToNewAlbumRow = 1;
for (const [i, album] of recentAlbums.entries()) {
rows.push({
type: AlbumModalRowType.ALBUM_ITEM,
selected: selectedRowIndex === i + selectedOffsetDueToNewAlbumRow,
multiSelected: multiSelectedAlbumIds.includes(album.id),
album,
});
}
}
if (!this.shared) {
rows.push({
type: AlbumModalRowType.SECTION,
text: (search.length === 0 ? $t('all_albums') : $t('albums')).toUpperCase(),
});
}
const selectedOffsetDueToNewAndRecents = 1 + recentAlbumsToShow.length;
for (const [i, album] of filteredAlbums.entries()) {
rows.push({
type: AlbumModalRowType.ALBUM_ITEM,
selected: selectedRowIndex === i + selectedOffsetDueToNewAndRecents,
multiSelected: multiSelectedAlbumIds.includes(album.id),
album,
});
}
} else if (albums.length > 0) {
rows.push({ type: AlbumModalRowType.MESSAGE, text: $t('no_albums_with_name_yet') });
} else {
rows.push({ type: AlbumModalRowType.MESSAGE, text: $t('no_albums_yet') });
}
return rows;
}
}