mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
Feature - Implemented virtual scroll on web (#573)
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)
This commit is contained in:
parent
bd92dde117
commit
552340add7
58 changed files with 2197 additions and 698 deletions
150
web/src/lib/stores/asset-interaction.store.ts
Normal file
150
web/src/lib/stores/asset-interaction.store.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue