upload-panel.svelte 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  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 { getAssetsInfo } from '$lib/stores/assets';
  9. let showDetail = true;
  10. let uploadLength = 0;
  11. const showUploadImageThumbnail = async (a: UploadAsset) => {
  12. const extension = a.fileExtension.toLowerCase();
  13. if (extension == 'jpeg' || extension == 'jpg' || extension == 'png') {
  14. try {
  15. const imgData = await a.file.arrayBuffer();
  16. const arrayBufferView = new Uint8Array(imgData);
  17. const blob = new Blob([arrayBufferView], { type: 'image/jpeg' });
  18. const urlCreator = window.URL || window.webkitURL;
  19. const imageUrl = urlCreator.createObjectURL(blob);
  20. const img: any = document.getElementById(`${a.id}`);
  21. img.src = imageUrl;
  22. } catch (e) {}
  23. }
  24. };
  25. function getSizeInHumanReadableFormat(sizeInByte: number) {
  26. const pepibyte = 1.126 * Math.pow(10, 15);
  27. const tebibyte = 1.1 * Math.pow(10, 12);
  28. const gibibyte = 1.074 * Math.pow(10, 9);
  29. const mebibyte = 1.049 * Math.pow(10, 6);
  30. const kibibyte = 1024;
  31. // Pebibyte
  32. if (sizeInByte >= pepibyte) {
  33. // Pe
  34. return `${(sizeInByte / pepibyte).toFixed(1)}PB`;
  35. } else if (tebibyte <= sizeInByte && sizeInByte < pepibyte) {
  36. // Te
  37. return `${(sizeInByte / tebibyte).toFixed(1)}TB`;
  38. } else if (gibibyte <= sizeInByte && sizeInByte < tebibyte) {
  39. // Gi
  40. return `${(sizeInByte / gibibyte).toFixed(1)}GB`;
  41. } else if (mebibyte <= sizeInByte && sizeInByte < gibibyte) {
  42. // Mega
  43. return `${(sizeInByte / mebibyte).toFixed(1)}MB`;
  44. } else if (kibibyte <= sizeInByte && sizeInByte < mebibyte) {
  45. // Kibi
  46. return `${(sizeInByte / kibibyte).toFixed(1)}KB`;
  47. } else {
  48. return `${sizeInByte}B`;
  49. }
  50. }
  51. // Reactive action to get thumbnail image of upload asset whenever there is a new one added to the list
  52. $: {
  53. if ($uploadAssetsStore.length != uploadLength) {
  54. $uploadAssetsStore.map((asset) => {
  55. showUploadImageThumbnail(asset);
  56. });
  57. uploadLength = $uploadAssetsStore.length;
  58. }
  59. }
  60. $: {
  61. if (showDetail) {
  62. $uploadAssetsStore.map((asset) => {
  63. showUploadImageThumbnail(asset);
  64. });
  65. }
  66. }
  67. let isUploading = false;
  68. uploadAssetsStore.isUploading.subscribe((value) => {
  69. isUploading = value;
  70. });
  71. </script>
  72. {#if isUploading}
  73. <div
  74. in:fade={{ duration: 250 }}
  75. out:fade={{ duration: 250, delay: 1000 }}
  76. on:outroend={() => getAssetsInfo()}
  77. class="absolute right-6 bottom-6 z-[10000]"
  78. >
  79. {#if showDetail}
  80. <div
  81. in:scale={{ duration: 250, easing: quartInOut }}
  82. class="bg-gray-200 p-4 text-sm w-[300px] rounded-lg shadow-sm border "
  83. >
  84. <div class="flex justify-between place-item-center mb-4">
  85. <p class="text-xs text-gray-500">UPLOADING {$uploadAssetsStore.length}</p>
  86. <button
  87. on:click={() => (showDetail = false)}
  88. class="w-[20px] h-[20px] bg-gray-50 rounded-full flex place-items-center place-content-center transition-colors hover:bg-gray-100"
  89. >
  90. <WindowMinimize />
  91. </button>
  92. </div>
  93. <div class="max-h-[400px] overflow-y-auto pr-2 rounded-lg immich-scrollbar">
  94. {#each $uploadAssetsStore as uploadAsset}
  95. {#key uploadAsset.id}
  96. <div
  97. in:fade={{ duration: 250 }}
  98. out:fade={{ duration: 100 }}
  99. class="text-xs mt-3 rounded-lg bg-immich-bg grid grid-cols-[70px_auto] gap-2 h-[70px]"
  100. >
  101. <div class="relative">
  102. <img
  103. in:fade={{ duration: 250 }}
  104. id={`${uploadAsset.id}`}
  105. src="/immich-logo.svg"
  106. alt=""
  107. class="h-[70px] w-[70px] object-cover rounded-tl-lg rounded-bl-lg "
  108. />
  109. <div class="bottom-0 left-0 absolute w-full h-[25px] bg-immich-primary/30">
  110. <p
  111. class="absolute bottom-1 right-1 object-right-bottom text-gray-50/95 font-semibold stroke-immich-primary uppercase"
  112. >
  113. .{uploadAsset.fileExtension}
  114. </p>
  115. </div>
  116. </div>
  117. <div class="p-2 pr-4 flex flex-col justify-between">
  118. <input
  119. disabled
  120. class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
  121. value={`[${getSizeInHumanReadableFormat(uploadAsset.file.size)}] ${
  122. uploadAsset.file.name
  123. }`}
  124. />
  125. <div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
  126. <div
  127. class="bg-immich-primary h-[15px] rounded-md transition-all"
  128. style={`width: ${uploadAsset.progress}%`}
  129. />
  130. <p class="absolute h-full w-full text-center top-0 text-[10px] ">
  131. {uploadAsset.progress}/100
  132. </p>
  133. </div>
  134. </div>
  135. </div>
  136. {/key}
  137. {/each}
  138. </div>
  139. </div>
  140. {:else}
  141. <div class="rounded-full">
  142. <button
  143. in:scale={{ duration: 250, easing: quartInOut }}
  144. on:click={() => (showDetail = true)}
  145. 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"
  146. >
  147. {$uploadAssetsStore.length}
  148. </button>
  149. <button
  150. in:scale={{ duration: 250, easing: quartInOut }}
  151. on:click={() => (showDetail = true)}
  152. class="bg-gray-300 p-5 rounded-full w-16 h-16 flex place-items-center place-content-center text-sm shadow-lg "
  153. >
  154. <div class="animate-pulse">
  155. <CloudUploadOutline size="30" color="#4250af" />
  156. </div>
  157. </button>
  158. </div>
  159. {/if}
  160. </div>
  161. {/if}