mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -04:00 
			
		
		
		
	
							parent
							
								
									b4b654b53f
								
							
						
					
					
						commit
						c896fe393f
					
				| @ -1,7 +1,7 @@ | ||||
| <script lang="ts"> | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { locale } from '$lib/stores/preferences.store'; | ||||
|   import { asByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units'; | ||||
|   import { getByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units'; | ||||
|   import type { ServerStatsResponseDto } from '@immich/sdk'; | ||||
|   import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js'; | ||||
|   import StatsCard from './stats-card.svelte'; | ||||
| @ -102,9 +102,9 @@ | ||||
|             <td class="w-1/4 text-ellipsis px-2 text-sm">{user.photos.toLocaleString($locale)}</td> | ||||
|             <td class="w-1/4 text-ellipsis px-2 text-sm">{user.videos.toLocaleString($locale)}</td> | ||||
|             <td class="w-1/4 text-ellipsis px-2 text-sm"> | ||||
|               {asByteUnitString(user.usage, $locale, 0)} | ||||
|               {getByteUnitString(user.usage, $locale, 0)} | ||||
|               {#if user.quotaSizeInBytes} | ||||
|                 / {asByteUnitString(user.quotaSizeInBytes, $locale, 0)} | ||||
|                 / {getByteUnitString(user.quotaSizeInBytes, $locale, 0)} | ||||
|               {/if} | ||||
|               <span class="text-immich-primary dark:text-immich-dark-primary"> | ||||
|                 {#if user.quotaSizeInBytes} | ||||
|  | ||||
| @ -1,10 +1,11 @@ | ||||
| <script lang="ts"> | ||||
|   import Icon from '$lib/components/elements/icon.svelte'; | ||||
|   import { ByteUnit } from '$lib/utils/byte-units'; | ||||
| 
 | ||||
|   export let icon: string; | ||||
|   export let title: string; | ||||
|   export let value: number; | ||||
|   export let unit: string | undefined = undefined; | ||||
|   export let unit: ByteUnit | undefined = undefined; | ||||
| 
 | ||||
|   $: zeros = () => { | ||||
|     const maxLength = 13; | ||||
| @ -26,7 +27,7 @@ | ||||
|       class="text-immich-primary dark:text-immich-dark-primary">{value}</span | ||||
|     > | ||||
|     {#if unit} | ||||
|       <span class="absolute -top-5 right-2 text-base font-light text-gray-400">{unit}</span> | ||||
|       <span class="absolute -top-5 right-2 text-base font-light text-gray-400">{ByteUnit[unit]}</span> | ||||
|     {/if} | ||||
|   </div> | ||||
| </div> | ||||
|  | ||||
| @ -32,7 +32,7 @@ | ||||
|   import { DateTime } from 'luxon'; | ||||
|   import { createEventDispatcher, onMount } from 'svelte'; | ||||
|   import { slide } from 'svelte/transition'; | ||||
|   import { asByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { getByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; | ||||
|   import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; | ||||
| @ -372,7 +372,7 @@ | ||||
|               {@const { width, height } = getDimensions(asset.exifInfo)} | ||||
|               <p>{width} x {height}</p> | ||||
|             {/if} | ||||
|             <p>{asByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p> | ||||
|             <p>{getByteUnitString(asset.exifInfo.fileSizeInByte, $locale)}</p> | ||||
|           </div> | ||||
|           {#if showAssetPath} | ||||
|             <p class="text-xs opacity-50 break-all" transition:slide={{ duration: 250 }}> | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|   import { type DownloadProgress, downloadAssets, downloadManager, isDownloading } from '$lib/stores/download'; | ||||
|   import { locale } from '$lib/stores/preferences.store'; | ||||
|   import { fly, slide } from 'svelte/transition'; | ||||
|   import { asByteUnitString } from '../../utils/byte-units'; | ||||
|   import { getByteUnitString } from '../../utils/byte-units'; | ||||
|   import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; | ||||
|   import { mdiClose } from '@mdi/js'; | ||||
|   import { t } from 'svelte-i18n'; | ||||
| @ -27,7 +27,7 @@ | ||||
|             <div class="flex place-items-center justify-between gap-2 text-xs font-medium"> | ||||
|               <p class="truncate">■ {downloadKey}</p> | ||||
|               {#if download.total} | ||||
|                 <p class="whitespace-nowrap">{asByteUnitString(download.total, $locale)}</p> | ||||
|                 <p class="whitespace-nowrap">{getByteUnitString(download.total, $locale)}</p> | ||||
|               {/if} | ||||
|             </div> | ||||
|             <div class="flex place-items-center gap-2"> | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| <script lang="ts"> | ||||
|   import { serverInfo } from '$lib/stores/server-info.store'; | ||||
|   import { convertToBytes } from '$lib/utils/byte-converter'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import { createUserAdmin } from '@immich/sdk'; | ||||
|   import { createEventDispatcher } from 'svelte'; | ||||
| @ -10,6 +9,7 @@ | ||||
|   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | ||||
|   import { featureFlags } from '$lib/stores/server-config.store'; | ||||
|   import { t } from 'svelte-i18n'; | ||||
|   import { ByteUnit, convertToBytes } from '$lib/utils/byte-units'; | ||||
| 
 | ||||
|   export let onClose: () => void; | ||||
| 
 | ||||
| @ -27,7 +27,7 @@ | ||||
|   let quotaSize: number | undefined; | ||||
|   let isCreatingUser = false; | ||||
| 
 | ||||
|   $: quotaSizeInBytes = quotaSize ? convertToBytes(quotaSize, 'GiB') : null; | ||||
|   $: quotaSizeInBytes = quotaSize ? convertToBytes(quotaSize, ByteUnit.GiB) : null; | ||||
|   $: quotaSizeWarning = quotaSizeInBytes && quotaSizeInBytes > $serverInfo.diskSizeRaw; | ||||
| 
 | ||||
|   $: { | ||||
|  | ||||
| @ -2,7 +2,6 @@ | ||||
|   import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; | ||||
|   import { AppRoute } from '$lib/constants'; | ||||
|   import { serverInfo } from '$lib/stores/server-info.store'; | ||||
|   import { convertFromBytes, convertToBytes } from '$lib/utils/byte-converter'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import { updateUserAdmin, type UserAdminResponseDto } from '@immich/sdk'; | ||||
|   import { mdiAccountEditOutline } from '@mdi/js'; | ||||
| @ -10,6 +9,7 @@ | ||||
|   import Button from '../elements/buttons/button.svelte'; | ||||
|   import { dialogController } from '$lib/components/shared-components/dialog/dialog'; | ||||
|   import { t } from 'svelte-i18n'; | ||||
|   import { ByteUnit, convertFromBytes, convertToBytes } from '$lib/utils/byte-units'; | ||||
| 
 | ||||
|   export let user: UserAdminResponseDto; | ||||
|   export let canResetPassword = true; | ||||
| @ -18,14 +18,14 @@ | ||||
| 
 | ||||
|   let error: string; | ||||
|   let success: string; | ||||
|   let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, 'GiB') : null; | ||||
|   let quotaSize = user.quotaSizeInBytes ? convertFromBytes(user.quotaSizeInBytes, ByteUnit.GiB) : null; | ||||
| 
 | ||||
|   const previousQutoa = user.quotaSizeInBytes; | ||||
| 
 | ||||
|   $: quotaSizeWarning = | ||||
|     previousQutoa !== convertToBytes(Number(quotaSize), 'GiB') && | ||||
|     previousQutoa !== convertToBytes(Number(quotaSize), ByteUnit.GiB) && | ||||
|     !!quotaSize && | ||||
|     convertToBytes(Number(quotaSize), 'GiB') > $serverInfo.diskSizeRaw; | ||||
|     convertToBytes(Number(quotaSize), ByteUnit.GiB) > $serverInfo.diskSizeRaw; | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ | ||||
|     close: void; | ||||
| @ -42,7 +42,7 @@ | ||||
|           email, | ||||
|           name, | ||||
|           storageLabel: storageLabel || '', | ||||
|           quotaSizeInBytes: quotaSize ? convertToBytes(Number(quotaSize), 'GiB') : null, | ||||
|           quotaSizeInBytes: quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null, | ||||
|         }, | ||||
|       }); | ||||
| 
 | ||||
|  | ||||
| @ -3,7 +3,7 @@ | ||||
|   import { locale } from '$lib/stores/preferences.store'; | ||||
|   import { websocketStore } from '$lib/stores/websocket'; | ||||
|   import { onMount } from 'svelte'; | ||||
|   import { asByteUnitString } from '../../utils/byte-units'; | ||||
|   import { getByteUnitString } from '../../utils/byte-units'; | ||||
|   import LoadingSpinner from './loading-spinner.svelte'; | ||||
|   import { mdiChartPie, mdiDns } from '@mdi/js'; | ||||
|   import { serverInfo } from '$lib/stores/server-info.store'; | ||||
| @ -47,7 +47,7 @@ | ||||
| <div class="dark:text-immich-dark-fg"> | ||||
|   <div | ||||
|     class="storage-status grid grid-cols-[64px_auto]" | ||||
|     title="Used {asByteUnitString(usedBytes, $locale, 3)} of {asByteUnitString(availableBytes, $locale, 3)}" | ||||
|     title="Used {getByteUnitString(usedBytes, $locale, 3)} of {getByteUnitString(availableBytes, $locale, 3)}" | ||||
|   > | ||||
|     <div class="pb-[2.15rem] pl-5 pr-6 text-immich-primary dark:text-immich-dark-primary group-hover:sm:pb-0 md:pb-0"> | ||||
|       <Icon path={mdiChartPie} size="24" /> | ||||
| @ -61,8 +61,8 @@ | ||||
|         <p class="text-xs"> | ||||
|           {$t('storage_usage', { | ||||
|             values: { | ||||
|               used: asByteUnitString(usedBytes, $locale), | ||||
|               available: asByteUnitString(availableBytes, $locale), | ||||
|               used: getByteUnitString(usedBytes, $locale), | ||||
|               available: getByteUnitString(availableBytes, $locale), | ||||
|             }, | ||||
|           })} | ||||
|         </p> | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|   import type { UploadAsset } from '$lib/models/upload-asset'; | ||||
|   import { UploadState } from '$lib/models/upload-asset'; | ||||
|   import { locale } from '$lib/stores/preferences.store'; | ||||
|   import { asByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { getByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { fade } from 'svelte/transition'; | ||||
|   import ImmichLogo from './immich-logo.svelte'; | ||||
|   import { getFilenameExtension } from '$lib/utils/asset-utils'; | ||||
| @ -42,7 +42,7 @@ | ||||
|       <input | ||||
|         disabled | ||||
|         class="w-full rounded-md border bg-gray-100 p-1 px-2 text-[10px] dark:border-immich-dark-gray dark:bg-gray-900" | ||||
|         value={`[${asByteUnitString(uploadAsset.file.size, $locale)}] ${uploadAsset.file.name}`} | ||||
|         value={`[${getByteUnitString(uploadAsset.file.size, $locale)}] ${uploadAsset.file.name}`} | ||||
|       /> | ||||
| 
 | ||||
|       <div | ||||
| @ -55,7 +55,7 @@ | ||||
|             {#if uploadAsset.message} | ||||
|               {uploadAsset.message} | ||||
|             {:else} | ||||
|               {uploadAsset.progress}% - {asByteUnitString(uploadAsset.speed || 0, $locale)}/s - {uploadAsset.eta}s | ||||
|               {uploadAsset.progress}% - {getByteUnitString(uploadAsset.speed || 0, $locale)}/s - {uploadAsset.eta}s | ||||
|             {/if} | ||||
|           </p> | ||||
|         {:else if uploadAsset.state === UploadState.PENDING} | ||||
|  | ||||
| @ -13,13 +13,13 @@ | ||||
|   import SettingInputField, { | ||||
|     SettingInputFieldType, | ||||
|   } from '$lib/components/shared-components/settings/setting-input-field.svelte'; | ||||
|   import { convertFromBytes, convertToBytes } from '$lib/utils/byte-converter'; | ||||
|   import { ByteUnit, convertFromBytes, convertToBytes } from '$lib/utils/byte-units'; | ||||
| 
 | ||||
|   let archiveSize = convertFromBytes($preferences?.download?.archiveSize || 4, 'GiB'); | ||||
|   let archiveSize = convertFromBytes($preferences?.download?.archiveSize || 4, ByteUnit.GiB); | ||||
| 
 | ||||
|   const handleSave = async () => { | ||||
|     try { | ||||
|       const dto = { download: { archiveSize: Math.floor(convertToBytes(archiveSize, 'GiB')) } }; | ||||
|       const dto = { download: { archiveSize: Math.floor(convertToBytes(archiveSize, ByteUnit.GiB)) } }; | ||||
|       const newPreferences = await updateMyPreferences({ userPreferencesUpdateDto: dto }); | ||||
|       $preferences = newPreferences; | ||||
| 
 | ||||
|  | ||||
| @ -8,7 +8,7 @@ import { downloadManager } from '$lib/stores/download'; | ||||
| import { preferences } from '$lib/stores/user.store'; | ||||
| import { downloadRequest, getKey, s, withError } from '$lib/utils'; | ||||
| import { createAlbum } from '$lib/utils/album-utils'; | ||||
| import { asByteUnitString } from '$lib/utils/byte-units'; | ||||
| import { getByteUnitString } from '$lib/utils/byte-units'; | ||||
| import { encodeHTMLSpecialChars } from '$lib/utils/string-utils'; | ||||
| import { | ||||
|   addAssetsToAlbum as addAssets, | ||||
| @ -232,7 +232,7 @@ export function isFlipped(orientation?: string | null) { | ||||
| 
 | ||||
| export function getFileSize(asset: AssetResponseDto): string { | ||||
|   const size = asset.exifInfo?.fileSizeInByte || 0; | ||||
|   return size > 0 ? asByteUnitString(size, undefined, 4) : 'Invalid Data'; | ||||
|   return size > 0 ? getByteUnitString(size, undefined, 4) : 'Invalid Data'; | ||||
| } | ||||
| 
 | ||||
| export function getAssetResolution(asset: AssetResponseDto): string { | ||||
|  | ||||
| @ -1,37 +0,0 @@ | ||||
| /** | ||||
|  * Convert to bytes from on a specified unit. | ||||
|  * | ||||
|  * * `1, 'GiB'`, returns `1073741824` bytes | ||||
|  * | ||||
|  * @param size value to be converted | ||||
|  * @param unit unit to convert from | ||||
|  * @returns bytes (number) | ||||
|  */ | ||||
| export function convertToBytes(size: number, unit: 'GiB'): number { | ||||
|   let bytes = 0; | ||||
| 
 | ||||
|   if (unit === 'GiB') { | ||||
|     bytes = size * 1_073_741_824; | ||||
|   } | ||||
| 
 | ||||
|   return bytes; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Convert from bytes to a specified unit. | ||||
|  * | ||||
|  * * `11073741824, 'GiB'`, returns `1` GiB | ||||
|  * | ||||
|  * @param bytes value to be converted | ||||
|  * @param unit unit to convert to | ||||
|  * @returns bytes (number) | ||||
|  */ | ||||
| export function convertFromBytes(bytes: number, unit: 'GiB'): number { | ||||
|   let size = 0; | ||||
| 
 | ||||
|   if (unit === 'GiB') { | ||||
|     size = bytes / 1_073_741_824; | ||||
|   } | ||||
| 
 | ||||
|   return size; | ||||
| } | ||||
							
								
								
									
										25
									
								
								web/src/lib/utils/byte-units.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								web/src/lib/utils/byte-units.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| import { ByteUnit, getByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units'; | ||||
| 
 | ||||
| describe('getBytesWithUnit', () => { | ||||
|   const tests = [ | ||||
|     { bytes: 0, expected: [0, ByteUnit.B] }, | ||||
|     { bytes: 42 * 2 ** 20, expected: [42, ByteUnit.MiB] }, | ||||
|     { bytes: 69 * 2 ** 20 + 420 * 2 ** 19, expected: [279, ByteUnit.MiB] }, | ||||
|     { bytes: 42 + 1337, maxPrecision: 3, expected: [1.347, ByteUnit.KiB] }, | ||||
|     { bytes: 42 + 69, expected: [111, ByteUnit.B] }, | ||||
|     { bytes: 2 ** 30 - 1, expected: [1024, ByteUnit.MiB] }, | ||||
|     { bytes: 2 ** 30, expected: [1, ByteUnit.GiB] }, | ||||
|     { bytes: 2 ** 30 + 1, expected: [1, ByteUnit.GiB] }, | ||||
|   ]; | ||||
|   for (const { bytes, maxPrecision, expected } of tests) { | ||||
|     it(`${bytes} should be split up in the factor ${expected[0]} and unit ${expected[1]}`, () => { | ||||
|       expect(getBytesWithUnit(bytes, maxPrecision)).toEqual(expected); | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| describe('asByteUnitString', () => { | ||||
|   it('should correctly return string', () => { | ||||
|     expect(getByteUnitString(42 * 2 ** 20)).toEqual('42 MiB'); | ||||
|   }); | ||||
| }); | ||||
| @ -1,3 +1,13 @@ | ||||
| export enum ByteUnit { | ||||
|   'B' = 0, | ||||
|   'KiB' = 1, | ||||
|   'MiB' = 2, | ||||
|   'GiB' = 3, | ||||
|   'TiB' = 4, | ||||
|   'PiB' = 5, | ||||
|   'EiB' = 6, | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Convert bytes to best human readable unit and number of that unit. | ||||
|  * | ||||
| @ -8,23 +18,10 @@ | ||||
|  * @param maxPrecision maximum number of decimal places, default is `1` | ||||
|  * @returns size (number) and unit (string) | ||||
|  */ | ||||
| export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, string] { | ||||
|   const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; | ||||
| export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, ByteUnit] { | ||||
|   const magnitude = Math.floor(Math.log(bytes === 0 ? 1 : bytes) / Math.log(1024)); | ||||
| 
 | ||||
|   let magnitude = 0; | ||||
|   let remainder = bytes; | ||||
|   while (remainder >= 1024) { | ||||
|     if (magnitude + 1 < units.length) { | ||||
|       magnitude++; | ||||
|       remainder /= 1024; | ||||
|     } else { | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   remainder = Number.parseFloat(remainder.toFixed(maxPrecision)); | ||||
| 
 | ||||
|   return [remainder, units[magnitude]]; | ||||
|   return [Number.parseFloat((bytes / 1024 ** magnitude).toFixed(maxPrecision)), magnitude]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
| @ -38,7 +35,33 @@ export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, stri | ||||
|  * @param maxPrecision maximum number of decimal places, default is `1` | ||||
|  * @returns localized bytes with unit as string | ||||
|  */ | ||||
| export function asByteUnitString(bytes: number, locale?: string, maxPrecision = 1): string { | ||||
| export function getByteUnitString(bytes: number, locale?: string, maxPrecision = 1): string { | ||||
|   const [size, unit] = getBytesWithUnit(bytes, maxPrecision); | ||||
|   return `${size.toLocaleString(locale)} ${unit}`; | ||||
|   return `${size.toLocaleString(locale)} ${ByteUnit[unit]}`; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Convert to bytes from on a specified unit. | ||||
|  * | ||||
|  * * `1, 'GiB'`, returns `1073741824` bytes | ||||
|  * | ||||
|  * @param size value to be converted | ||||
|  * @param unit unit to convert from | ||||
|  * @returns bytes (number) | ||||
|  */ | ||||
| export function convertToBytes(size: number, unit: ByteUnit): number { | ||||
|   return size * 1024 ** unit; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Convert from bytes to a specified unit. | ||||
|  * | ||||
|  * * `11073741824, 'GiB'`, returns `1` GiB | ||||
|  * | ||||
|  * @param bytes value to be converted | ||||
|  * @param unit unit to convert to | ||||
|  * @returns bytes (number) | ||||
|  */ | ||||
| export function convertFromBytes(bytes: number, unit: ByteUnit): number { | ||||
|   return bytes / 1024 ** unit; | ||||
| } | ||||
|  | ||||
| @ -14,7 +14,7 @@ | ||||
|     NotificationType, | ||||
|   } from '$lib/components/shared-components/notification/notification'; | ||||
|   import Portal from '$lib/components/shared-components/portal/portal.svelte'; | ||||
|   import { getBytesWithUnit } from '$lib/utils/byte-units'; | ||||
|   import { ByteUnit, getBytesWithUnit } from '$lib/utils/byte-units'; | ||||
|   import { getContextMenuPosition } from '$lib/utils/context-menu'; | ||||
|   import { handleError } from '$lib/utils/handle-error'; | ||||
|   import { | ||||
| @ -49,7 +49,7 @@ | ||||
|   let videos: number[] = []; | ||||
|   let totalCount: number[] = []; | ||||
|   let diskUsage: number[] = []; | ||||
|   let diskUsageUnit: string[] = []; | ||||
|   let diskUsageUnit: ByteUnit[] = []; | ||||
| 
 | ||||
|   let confirmDeleteLibrary: LibraryResponseDto | null = null; | ||||
|   let deletedLibrary: LibraryResponseDto | null = null; | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
|   import { user } from '$lib/stores/user.store'; | ||||
|   import { websocketEvents } from '$lib/stores/websocket'; | ||||
|   import { copyToClipboard } from '$lib/utils'; | ||||
|   import { asByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { getByteUnitString } from '$lib/utils/byte-units'; | ||||
|   import { UserStatus, searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk'; | ||||
|   import { mdiClose, mdiContentCopy, mdiDeleteRestore, mdiPencilOutline, mdiTrashCanOutline } from '@mdi/js'; | ||||
|   import { DateTime } from 'luxon'; | ||||
| @ -215,7 +215,7 @@ | ||||
|                 <td class="hidden xl:block w-3/12 2xl:w-2/12 text-ellipsis break-all px-2 text-sm"> | ||||
|                   <div class="container mx-auto flex flex-wrap justify-center"> | ||||
|                     {#if immichUser.quotaSizeInBytes && immichUser.quotaSizeInBytes > 0} | ||||
|                       {asByteUnitString(immichUser.quotaSizeInBytes, $locale)} | ||||
|                       {getByteUnitString(immichUser.quotaSizeInBytes, $locale)} | ||||
|                     {:else} | ||||
|                       <Icon path={mdiClose} size="16" /> | ||||
|                     {/if} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user