Remove generics from AssetInteraction

This commit is contained in:
Min Idzelis 2025-04-20 03:47:51 +00:00
parent 9b7e9bc7b8
commit f3fe043c22
15 changed files with 43 additions and 45 deletions

View File

@ -4,7 +4,7 @@
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils'; import { cancelMultiselect, downloadAlbum } from '$lib/utils/asset-utils';
@ -36,7 +36,7 @@
$effect(() => void assetStore.updateOptions({ albumId: album.id, order: album.order })); $effect(() => void assetStore.updateOptions({ albumId: album.id, order: album.order }));
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
dragAndDropFilesStore.subscribe((value) => { dragAndDropFilesStore.subscribe((value) => {
if (value.isDragging && value.files.length > 0) { if (value.isDragging && value.files.length > 0) {

View File

@ -3,8 +3,7 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { getKey } from '$lib/utils'; import { getKey } from '$lib/utils';
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils'; import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
import { isTimelineAsset } from '$lib/utils/timeline-util'; import { getAssetInfo } from '@immich/sdk';
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
import { mdiCloudDownloadOutline, mdiFileDownloadOutline, mdiFolderDownloadOutline } from '@mdi/js'; import { mdiCloudDownloadOutline, mdiFileDownloadOutline, mdiFolderDownloadOutline } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
@ -23,10 +22,7 @@
const assets = [...getAssets()]; const assets = [...getAssets()];
if (assets.length === 1) { if (assets.length === 1) {
clearSelect(); clearSelect();
let asset: AssetResponseDto = assets[0] as AssetResponseDto; let asset = await getAssetInfo({ id: assets[0].id, key: getKey() });
if (isTimelineAsset(assets[0])) {
asset = await getAssetInfo({ id: assets[0].id, key: getKey() });
}
await downloadFile(asset); await downloadFile(asset);
return; return;
} }

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import type { AssetInteraction, BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { type AssetStore, isSelectingAllAssets } from '$lib/stores/assets-store.svelte'; import { type AssetStore, isSelectingAllAssets } from '$lib/stores/assets-store.svelte';
import { cancelMultiselect, selectAllAssets } from '$lib/utils/asset-utils'; import { cancelMultiselect, selectAllAssets } from '$lib/utils/asset-utils';
import { mdiSelectAll, mdiSelectRemove } from '@mdi/js'; import { mdiSelectAll, mdiSelectRemove } from '@mdi/js';
@ -8,7 +8,7 @@
interface Props { interface Props {
assetStore: AssetStore; assetStore: AssetStore;
assetInteraction: AssetInteraction<BaseInteractionAsset>; assetInteraction: AssetInteraction;
} }
let { assetStore, assetInteraction }: Props = $props(); let { assetStore, assetInteraction }: Props = $props();

View File

@ -11,7 +11,7 @@
import { navigate } from '$lib/utils/navigation'; import { navigate } from '$lib/utils/navigation';
import { getDateLocaleString } from '$lib/utils/timeline-util'; import { getDateLocaleString } from '$lib/utils/timeline-util';
import type { AssetInteraction, BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js'; import { mdiCheckCircle, mdiCircleOutline } from '@mdi/js';
import { fly, scale } from 'svelte/transition'; import { fly, scale } from 'svelte/transition';
import Thumbnail from '../assets/thumbnail/thumbnail.svelte'; import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
@ -29,7 +29,7 @@
showArchiveIcon: boolean; showArchiveIcon: boolean;
bucket: AssetBucket; bucket: AssetBucket;
assetStore: AssetStore; assetStore: AssetStore;
assetInteraction: AssetInteraction<BaseInteractionAsset>; assetInteraction: AssetInteraction;
onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void; onSelect: ({ title, assets }: { title: string; assets: TimelineAsset[] }) => void;
onSelectAssets: (asset: TimelineAsset) => void; onSelectAssets: (asset: TimelineAsset) => void;

View File

@ -41,7 +41,7 @@
additionally, update the page location/url with the asset as the asset-grid is scrolled */ additionally, update the page location/url with the asset as the asset-grid is scrolled */
enableRouting: boolean; enableRouting: boolean;
assetStore: AssetStore; assetStore: AssetStore;
assetInteraction: AssetInteraction<TimelineAsset>; assetInteraction: AssetInteraction;
removeAction?: AssetAction.UNARCHIVE | AssetAction.ARCHIVE | AssetAction.FAVORITE | AssetAction.UNFAVORITE | null; removeAction?: AssetAction.UNARCHIVE | AssetAction.ARCHIVE | AssetAction.FAVORITE | AssetAction.UNFAVORITE | null;
withStacked?: boolean; withStacked?: boolean;
showArchiveIcon?: boolean; showArchiveIcon?: boolean;

View File

@ -4,8 +4,8 @@
export interface AssetControlContext { export interface AssetControlContext {
// Wrap assets in a function, because context isn't reactive. // Wrap assets in a function, because context isn't reactive.
getAssets: () => BaseInteractionAsset[]; // All assets includes partners' assets getAssets: () => TimelineAsset[]; // All assets includes partners' assets
getOwnedAssets: () => BaseInteractionAsset[]; // Only assets owned by the user getOwnedAssets: () => TimelineAsset[]; // Only assets owned by the user
clearSelect: () => void; clearSelect: () => void;
} }
@ -14,13 +14,13 @@
</script> </script>
<script lang="ts"> <script lang="ts">
import type { BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import type { TimelineAsset } from '$lib/stores/assets-store.svelte';
import { mdiClose } from '@mdi/js'; import { mdiClose } from '@mdi/js';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ControlAppBar from '../shared-components/control-app-bar.svelte';
interface Props { interface Props {
assets: BaseInteractionAsset[]; assets: TimelineAsset[];
clearSelect: () => void; clearSelect: () => void;
ownerId?: string | undefined; ownerId?: string | undefined;
children?: Snippet; children?: Snippet;

View File

@ -1,18 +1,18 @@
import { AssetInteraction, type BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { resetSavedUser, user } from '$lib/stores/user.store'; import { resetSavedUser, user } from '$lib/stores/user.store';
import { assetFactory } from '@test-data/factories/asset-factory'; import { timelineAssetFactory } from '@test-data/factories/asset-factory';
import { userAdminFactory } from '@test-data/factories/user-factory'; import { userAdminFactory } from '@test-data/factories/user-factory';
describe('AssetInteraction', () => { describe('AssetInteraction', () => {
let assetInteraction: AssetInteraction<BaseInteractionAsset>; let assetInteraction: AssetInteraction;
beforeEach(() => { beforeEach(() => {
assetInteraction = new AssetInteraction(); assetInteraction = new AssetInteraction();
}); });
it('calculates derived values from selection', () => { it('calculates derived values from selection', () => {
assetInteraction.selectAsset(assetFactory.build({ isFavorite: true, isArchived: true, isTrashed: true })); assetInteraction.selectAsset(timelineAssetFactory.build({ isFavorite: true, isArchived: true, isTrashed: true }));
assetInteraction.selectAsset(assetFactory.build({ isFavorite: true, isArchived: false, isTrashed: false })); assetInteraction.selectAsset(timelineAssetFactory.build({ isFavorite: true, isArchived: false, isTrashed: false }));
expect(assetInteraction.selectionActive).toBe(true); expect(assetInteraction.selectionActive).toBe(true);
expect(assetInteraction.isAllTrashed).toBe(false); expect(assetInteraction.isAllTrashed).toBe(false);
@ -22,7 +22,7 @@ describe('AssetInteraction', () => {
it('updates isAllUserOwned when the active user changes', () => { it('updates isAllUserOwned when the active user changes', () => {
const [user1, user2] = userAdminFactory.buildList(2); const [user1, user2] = userAdminFactory.buildList(2);
assetInteraction.selectAsset(assetFactory.build({ ownerId: user1.id })); assetInteraction.selectAsset(timelineAssetFactory.build({ ownerId: user1.id }));
const cleanup = $effect.root(() => { const cleanup = $effect.root(() => {
expect(assetInteraction.isAllUserOwned).toBe(false); expect(assetInteraction.isAllUserOwned).toBe(false);

View File

@ -3,8 +3,13 @@ import FormatBoldMessage from '$lib/components/i18n/format-bold-message.svelte';
import type { InterpolationValues } from '$lib/components/i18n/format-message'; import type { InterpolationValues } from '$lib/components/i18n/format-message';
import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification'; import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import type { AssetInteraction, BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetsSnapshot, isSelectingAllAssets, type AssetStore } from '$lib/stores/assets-store.svelte'; import {
assetsSnapshot,
isSelectingAllAssets,
type AssetStore,
type TimelineAsset,
} from '$lib/stores/assets-store.svelte';
import { downloadManager } from '$lib/stores/download-store.svelte'; import { downloadManager } from '$lib/stores/download-store.svelte';
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { downloadRequest, getKey, withError } from '$lib/utils'; import { downloadRequest, getKey, withError } from '$lib/utils';
@ -364,7 +369,7 @@ export const getAssetType = (type: AssetTypeEnum) => {
} }
}; };
export const getSelectedAssets = (assets: BaseInteractionAsset[], user: UserResponseDto | null): string[] => { export const getSelectedAssets = (assets: TimelineAsset[], user: UserResponseDto | null): string[] => {
const ids = [...assets].filter((a) => user && a.ownerId === user.id).map((a) => a.id); const ids = [...assets].filter((a) => user && a.ownerId === user.id).map((a) => a.id);
const numberOfIssues = [...assets].filter((a) => user && a.ownerId !== user.id).length; const numberOfIssues = [...assets].filter((a) => user && a.ownerId !== user.id).length;
@ -467,10 +472,7 @@ export const keepThisDeleteOthers = async (keepAsset: AssetResponseDto, stack: S
} }
}; };
export const selectAllAssets = async ( export const selectAllAssets = async (assetStore: AssetStore, assetInteraction: AssetInteraction) => {
assetStore: AssetStore,
assetInteraction: AssetInteraction<BaseInteractionAsset>,
) => {
if (get(isSelectingAllAssets)) { if (get(isSelectingAllAssets)) {
// Selection is already ongoing // Selection is already ongoing
return; return;
@ -498,7 +500,7 @@ export const selectAllAssets = async (
} }
}; };
export const cancelMultiselect = (assetInteraction: AssetInteraction<BaseInteractionAsset>) => { export const cancelMultiselect = (assetInteraction: AssetInteraction) => {
isSelectingAllAssets.set(false); isSelectingAllAssets.set(false);
assetInteraction.clearMultiselect(); assetInteraction.clearMultiselect();
}; };

View File

@ -38,7 +38,7 @@
import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store'; import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { preferences, user } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
@ -107,8 +107,8 @@
let reactions: ActivityResponseDto[] = $state([]); let reactions: ActivityResponseDto[] = $state([]);
let albumOrder: AssetOrder | undefined = $state(data.album.order); let albumOrder: AssetOrder | undefined = $state(data.album.order);
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const timelineInteraction = new AssetInteraction<TimelineAsset>(); const timelineInteraction = new AssetInteraction();
afterNavigate(({ from }) => { afterNavigate(({ from }) => {
let url: string | undefined = from?.url?.pathname; let url: string | undefined = from?.url?.pathname;

View File

@ -14,7 +14,7 @@
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'; import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -29,7 +29,7 @@
void assetStore.updateOptions({ isArchived: true }); void assetStore.updateOptions({ isArchived: true });
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const handleEscape = () => { const handleEscape = () => {
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {

View File

@ -16,7 +16,7 @@
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import { AssetAction } from '$lib/constants'; import { AssetAction } from '$lib/constants';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
@ -33,7 +33,7 @@
void assetStore.updateOptions({ isFavorite: true, withStacked: true }); void assetStore.updateOptions({ isFavorite: true, withStacked: true });
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const handleEscape = () => { const handleEscape = () => {
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {

View File

@ -9,7 +9,7 @@
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { mdiArrowLeft, mdiPlus } from '@mdi/js'; import { mdiArrowLeft, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -24,7 +24,7 @@
const assetStore = new AssetStore(); const assetStore = new AssetStore();
$effect(() => void assetStore.updateOptions({ userId: data.partner.id, isArchived: false, withStacked: true })); $effect(() => void assetStore.updateOptions({ userId: data.partner.id, isArchived: false, withStacked: true }));
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const handleEscape = () => { const handleEscape = () => {
if (assetInteraction.selectionActive) { if (assetInteraction.selectionActive) {

View File

@ -71,7 +71,7 @@
$effect(() => void assetStore.updateOptions({ isArchived: false, personId: data.person.id })); $effect(() => void assetStore.updateOptions({ isArchived: false, personId: data.person.id }));
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS); let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
let isEditingName = $state(false); let isEditingName = $state(false);

View File

@ -17,7 +17,7 @@
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte'; import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
import { AppRoute, AssetAction, QueryParameter, SettingInputFieldType } from '$lib/constants'; import { AppRoute, AssetAction, QueryParameter, SettingInputFieldType } from '$lib/constants';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils'; import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
import { deleteTag, getAllTags, updateTag, upsertTags, type TagResponseDto } from '@immich/sdk'; import { deleteTag, getAllTags, updateTag, upsertTags, type TagResponseDto } from '@immich/sdk';
import { Button, HStack, Text } from '@immich/ui'; import { Button, HStack, Text } from '@immich/ui';
@ -35,7 +35,7 @@
let pathSegments = $derived(data.path ? data.path.split('/') : []); let pathSegments = $derived(data.path ? data.path.split('/') : []);
let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || ''); let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || '');
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const buildMap = (tags: TagResponseDto[]) => { const buildMap = (tags: TagResponseDto[]) => {
return Object.fromEntries(tags.map((tag) => [tag.value, tag])); return Object.fromEntries(tags.map((tag) => [tag.value, tag]));

View File

@ -15,7 +15,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
import { featureFlags, serverConfig } from '$lib/stores/server-config.store'; import { featureFlags, serverConfig } from '$lib/stores/server-config.store';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
@ -40,7 +40,7 @@
void assetStore.updateOptions({ isTrashed: true }); void assetStore.updateOptions({ isTrashed: true });
onDestroy(() => assetStore.destroy()); onDestroy(() => assetStore.destroy());
const assetInteraction = new AssetInteraction<TimelineAsset>(); const assetInteraction = new AssetInteraction();
const handleEmptyTrash = async () => { const handleEmptyTrash = async () => {
const isConfirmed = await dialogController.show({ const isConfirmed = await dialogController.show({