gallery-viewer.svelte 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. <script lang="ts">
  2. import { page } from '$app/stores';
  3. import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
  4. import { handleError } from '$lib/utils/handle-error';
  5. import { AssetResponseDto, ThumbnailFormat } from '@api';
  6. import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
  7. import { flip } from 'svelte/animate';
  8. import { getThumbnailSize } from '$lib/utils/thumbnail-util';
  9. import { assetViewingStore } from '$lib/stores/asset-viewing.store';
  10. import { onDestroy } from 'svelte';
  11. export let assets: AssetResponseDto[];
  12. export let selectedAssets: Set<AssetResponseDto> = new Set();
  13. export let disableAssetSelect = false;
  14. export let showArchiveIcon = false;
  15. let { isViewing: showAssetViewer } = assetViewingStore;
  16. let selectedAsset: AssetResponseDto;
  17. let currentViewAssetIndex = 0;
  18. let viewWidth: number;
  19. $: thumbnailSize = getThumbnailSize(assets.length, viewWidth);
  20. $: isMultiSelectionMode = selectedAssets.size > 0;
  21. const viewAssetHandler = (event: CustomEvent) => {
  22. const { asset }: { asset: AssetResponseDto } = event.detail;
  23. currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
  24. selectedAsset = assets[currentViewAssetIndex];
  25. $showAssetViewer = true;
  26. pushState(selectedAsset.id);
  27. };
  28. const selectAssetHandler = (event: CustomEvent) => {
  29. const { asset }: { asset: AssetResponseDto } = event.detail;
  30. let temp = new Set(selectedAssets);
  31. if (selectedAssets.has(asset)) {
  32. temp.delete(asset);
  33. } else {
  34. temp.add(asset);
  35. }
  36. selectedAssets = temp;
  37. };
  38. const navigateAssetForward = () => {
  39. try {
  40. if (currentViewAssetIndex < assets.length - 1) {
  41. currentViewAssetIndex++;
  42. selectedAsset = assets[currentViewAssetIndex];
  43. pushState(selectedAsset.id);
  44. }
  45. } catch (e) {
  46. handleError(e, 'Cannot navigate to the next asset');
  47. }
  48. };
  49. const navigateAssetBackward = () => {
  50. try {
  51. if (currentViewAssetIndex > 0) {
  52. currentViewAssetIndex--;
  53. selectedAsset = assets[currentViewAssetIndex];
  54. pushState(selectedAsset.id);
  55. }
  56. } catch (e) {
  57. handleError(e, 'Cannot navigate to previous asset');
  58. }
  59. };
  60. const pushState = (assetId: string) => {
  61. // add a URL to the browser's history
  62. // changes the current URL in the address bar but doesn't perform any SvelteKit navigation
  63. history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
  64. };
  65. const closeViewer = () => {
  66. $showAssetViewer = false;
  67. history.pushState(null, '', `${$page.url.pathname}`);
  68. };
  69. onDestroy(() => {
  70. $showAssetViewer = false;
  71. });
  72. </script>
  73. {#if assets.length > 0}
  74. <div class="flex w-full flex-wrap gap-1 pb-20" bind:clientWidth={viewWidth}>
  75. {#each assets as asset (asset.id)}
  76. <div animate:flip={{ duration: 500 }}>
  77. <Thumbnail
  78. {asset}
  79. {thumbnailSize}
  80. readonly={disableAssetSelect}
  81. format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
  82. on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
  83. on:select={selectAssetHandler}
  84. selected={selectedAssets.has(asset)}
  85. {showArchiveIcon}
  86. />
  87. </div>
  88. {/each}
  89. </div>
  90. {/if}
  91. <!-- Overlay Asset Viewer -->
  92. {#if $showAssetViewer}
  93. <AssetViewer
  94. asset={selectedAsset}
  95. on:previous={navigateAssetBackward}
  96. on:next={navigateAssetForward}
  97. on:close={closeViewer}
  98. />
  99. {/if}