upload-panel.svelte 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <script lang="ts">
  2. import { quartInOut } from 'svelte/easing';
  3. import { scale, fade } from 'svelte/transition';
  4. import { uploadAssetsStore } from '$lib/stores/upload';
  5. import CloudUploadOutline from 'svelte-material-icons/CloudUploadOutline.svelte';
  6. import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
  7. import type { UploadAsset } from '$lib/models/upload-asset';
  8. import { notificationController, NotificationType } from './notification/notification';
  9. import { getBytesWithUnit } from '../../utils/byte-units';
  10. let showDetail = true;
  11. let uploadLength = 0;
  12. const showUploadImageThumbnail = async (a: UploadAsset) => {
  13. const extension = a.fileExtension.toLowerCase();
  14. if (extension == 'jpeg' || extension == 'jpg' || extension == 'png') {
  15. try {
  16. const imgData = await a.file.arrayBuffer();
  17. const arrayBufferView = new Uint8Array(imgData);
  18. const blob = new Blob([arrayBufferView], { type: 'image/jpeg' });
  19. const urlCreator = window.URL || window.webkitURL;
  20. const imageUrl = urlCreator.createObjectURL(blob);
  21. // TODO: There is probably a cleaner way of doing this
  22. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  23. const img: any = document.getElementById(`${a.id}`);
  24. img.src = imageUrl;
  25. } catch {
  26. // Do nothing?
  27. }
  28. }
  29. };
  30. // Reactive action to get thumbnail image of upload asset whenever there is a new one added to the list
  31. $: {
  32. if ($uploadAssetsStore.length != uploadLength) {
  33. $uploadAssetsStore.map((asset) => {
  34. showUploadImageThumbnail(asset);
  35. });
  36. uploadLength = $uploadAssetsStore.length;
  37. }
  38. }
  39. $: {
  40. if (showDetail) {
  41. $uploadAssetsStore.map((asset) => {
  42. showUploadImageThumbnail(asset);
  43. });
  44. }
  45. }
  46. let isUploading = false;
  47. uploadAssetsStore.isUploading.subscribe((value) => {
  48. isUploading = value;
  49. });
  50. </script>
  51. {#if isUploading}
  52. <div
  53. in:fade={{ duration: 250 }}
  54. out:fade={{ duration: 250, delay: 1000 }}
  55. on:outroend={() => {
  56. notificationController.show({
  57. message: 'Upload success, refresh the page to see new upload assets',
  58. type: NotificationType.Info
  59. });
  60. }}
  61. class="absolute right-6 bottom-6 z-[10000]"
  62. >
  63. {#if showDetail}
  64. <div
  65. in:scale={{ duration: 250, easing: quartInOut }}
  66. class="bg-gray-200 p-4 text-sm w-[300px] rounded-lg shadow-sm border "
  67. >
  68. <div class="flex justify-between place-item-center mb-4">
  69. <p class="text-xs text-gray-500">UPLOADING {$uploadAssetsStore.length}</p>
  70. <button
  71. on:click={() => (showDetail = false)}
  72. class="w-[20px] h-[20px] bg-gray-50 rounded-full flex place-items-center place-content-center transition-colors hover:bg-gray-100"
  73. >
  74. <WindowMinimize />
  75. </button>
  76. </div>
  77. <div class="max-h-[400px] overflow-y-auto pr-2 rounded-lg immich-scrollbar">
  78. {#each $uploadAssetsStore as uploadAsset}
  79. {#key uploadAsset.id}
  80. <div
  81. in:fade={{ duration: 250 }}
  82. out:fade={{ duration: 100 }}
  83. class="text-xs mt-3 rounded-lg bg-immich-bg grid grid-cols-[70px_auto] gap-2 h-[70px]"
  84. >
  85. <div class="relative">
  86. <img
  87. in:fade={{ duration: 250 }}
  88. id={`${uploadAsset.id}`}
  89. src="/immich-logo.svg"
  90. alt=""
  91. class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg "
  92. />
  93. <div class="bottom-0 left-0 absolute w-full h-[25px] bg-immich-primary/30">
  94. <p
  95. class="absolute bottom-1 right-1 object-right-bottom text-gray-50/95 font-semibold stroke-immich-primary uppercase"
  96. >
  97. .{uploadAsset.fileExtension}
  98. </p>
  99. </div>
  100. </div>
  101. <div class="p-2 pr-4 flex flex-col justify-between">
  102. <input
  103. disabled
  104. class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
  105. value={`[${getBytesWithUnit(uploadAsset.file.size)}] ${
  106. uploadAsset.file.name
  107. }`}
  108. />
  109. <div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
  110. <div
  111. class="bg-immich-primary h-[15px] rounded-md transition-all"
  112. style={`width: ${uploadAsset.progress}%`}
  113. />
  114. <p class="absolute h-full w-full text-center top-0 text-[10px] ">
  115. {uploadAsset.progress}/100
  116. </p>
  117. </div>
  118. </div>
  119. </div>
  120. {/key}
  121. {/each}
  122. </div>
  123. </div>
  124. {:else}
  125. <div class="rounded-full">
  126. <button
  127. in:scale={{ duration: 250, easing: quartInOut }}
  128. on:click={() => (showDetail = true)}
  129. class="absolute -top-4 -left-4 text-xs rounded-full w-10 h-10 p-5 flex place-items-center place-content-center bg-immich-primary text-gray-200"
  130. >
  131. {$uploadAssetsStore.length}
  132. </button>
  133. <button
  134. in:scale={{ duration: 250, easing: quartInOut }}
  135. on:click={() => (showDetail = true)}
  136. class="bg-gray-300 p-5 rounded-full w-16 h-16 flex place-items-center place-content-center text-sm shadow-lg "
  137. >
  138. <div class="animate-pulse">
  139. <CloudUploadOutline size="30" color="#4250af" />
  140. </div>
  141. </button>
  142. </div>
  143. {/if}
  144. </div>
  145. {/if}