mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
This PR implemented a virtual scroll on the web, as seen in this article. [Building the Google Photos Web UI](https://medium.com/google-design/google-photos-45b714dfbed1)
151 lines
4.1 KiB
TypeScript
151 lines
4.1 KiB
TypeScript
import { AssetGridState } from '$lib/models/asset-grid-state';
|
|
import { api, AssetResponseDto } from '@api';
|
|
import { derived, writable } from 'svelte/store';
|
|
import { assetGridState, assetStore } from './assets.store';
|
|
import _ from 'lodash-es';
|
|
|
|
// Asset Viewer
|
|
export const viewingAssetStoreState = writable<AssetResponseDto>();
|
|
export const isViewingAssetStoreState = writable<boolean>(false);
|
|
|
|
// Multi-Selection mode
|
|
export const assetsInAlbumStoreState = writable<AssetResponseDto[]>([]);
|
|
export const selectedAssets = writable<Set<AssetResponseDto>>(new Set());
|
|
export const selectedGroup = writable<Set<string>>(new Set());
|
|
export const isMultiSelectStoreState = derived(
|
|
selectedAssets,
|
|
($selectedAssets) => $selectedAssets.size > 0
|
|
);
|
|
|
|
function createAssetInteractionStore() {
|
|
let _assetGridState = new AssetGridState();
|
|
let _viewingAssetStoreState: AssetResponseDto;
|
|
let _selectedAssets: Set<AssetResponseDto>;
|
|
let _selectedGroup: Set<string>;
|
|
let _assetsInAblums: AssetResponseDto[];
|
|
let savedAssetLength = 0;
|
|
let assetSortedByDate: AssetResponseDto[] = [];
|
|
|
|
// Subscriber
|
|
assetGridState.subscribe((state) => {
|
|
_assetGridState = state;
|
|
});
|
|
|
|
viewingAssetStoreState.subscribe((asset) => {
|
|
_viewingAssetStoreState = asset;
|
|
});
|
|
|
|
selectedAssets.subscribe((assets) => {
|
|
_selectedAssets = assets;
|
|
});
|
|
|
|
selectedGroup.subscribe((group) => {
|
|
_selectedGroup = group;
|
|
});
|
|
|
|
assetsInAlbumStoreState.subscribe((assets) => {
|
|
_assetsInAblums = assets;
|
|
});
|
|
|
|
// Methods
|
|
|
|
/**
|
|
* Asset Viewer
|
|
*/
|
|
const setViewingAsset = async (asset: AssetResponseDto) => {
|
|
const { data } = await api.assetApi.getAssetById(asset.id);
|
|
viewingAssetStoreState.set(data);
|
|
isViewingAssetStoreState.set(true);
|
|
};
|
|
|
|
const setIsViewingAsset = (isViewing: boolean) => {
|
|
isViewingAssetStoreState.set(isViewing);
|
|
};
|
|
|
|
const navigateAsset = async (direction: 'next' | 'previous') => {
|
|
// Flatten and sort the asset by date if there are new assets
|
|
if (assetSortedByDate.length === 0 || savedAssetLength !== _assetGridState.assets.length) {
|
|
assetSortedByDate = _.sortBy(_assetGridState.assets, (a) => a.createdAt);
|
|
savedAssetLength = _assetGridState.assets.length;
|
|
}
|
|
|
|
// Find the index of the current asset
|
|
const currentIndex = assetSortedByDate.findIndex((a) => a.id === _viewingAssetStoreState.id);
|
|
|
|
// Get the next or previous asset
|
|
const nextIndex = direction === 'previous' ? currentIndex + 1 : currentIndex - 1;
|
|
|
|
// Run out of asset, this might be because there is no asset in the next bucket.
|
|
if (nextIndex == -1) {
|
|
let nextBucket = '';
|
|
// Find next bucket that doesn't have all assets loaded
|
|
|
|
for (const bucket of _assetGridState.buckets) {
|
|
if (bucket.assets.length === 0) {
|
|
nextBucket = bucket.bucketDate;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nextBucket !== '') {
|
|
await assetStore.getAssetsByBucket(nextBucket);
|
|
navigateAsset(direction);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const nextAsset = assetSortedByDate[nextIndex];
|
|
setViewingAsset(nextAsset);
|
|
};
|
|
|
|
/**
|
|
* Multiselect
|
|
*/
|
|
const addAssetToMultiselectGroup = (asset: AssetResponseDto) => {
|
|
// Not select if in album alreaady
|
|
if (_assetsInAblums.find((a) => a.id === asset.id)) {
|
|
return;
|
|
}
|
|
|
|
_selectedAssets.add(asset);
|
|
selectedAssets.set(_selectedAssets);
|
|
};
|
|
|
|
const removeAssetFromMultiselectGroup = (asset: AssetResponseDto) => {
|
|
_selectedAssets.delete(asset);
|
|
selectedAssets.set(_selectedAssets);
|
|
};
|
|
|
|
const addGroupToMultiselectGroup = (group: string) => {
|
|
_selectedGroup.add(group);
|
|
selectedGroup.set(_selectedGroup);
|
|
};
|
|
|
|
const removeGroupFromMultiselectGroup = (group: string) => {
|
|
_selectedGroup.delete(group);
|
|
selectedGroup.set(_selectedGroup);
|
|
};
|
|
|
|
const clearMultiselect = () => {
|
|
_selectedAssets.clear();
|
|
_selectedGroup.clear();
|
|
_assetsInAblums = [];
|
|
|
|
selectedAssets.set(_selectedAssets);
|
|
selectedGroup.set(_selectedGroup);
|
|
assetsInAlbumStoreState.set(_assetsInAblums);
|
|
};
|
|
return {
|
|
setViewingAsset,
|
|
setIsViewingAsset,
|
|
navigateAsset,
|
|
addAssetToMultiselectGroup,
|
|
removeAssetFromMultiselectGroup,
|
|
addGroupToMultiselectGroup,
|
|
removeGroupFromMultiselectGroup,
|
|
clearMultiselect
|
|
};
|
|
}
|
|
|
|
export const assetInteractionStore = createAssetInteractionStore();
|