Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(web): byte unit utils #10332

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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>
4 changes: 2 additions & 2 deletions web/src/lib/components/asset-viewer/detail-panel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 }}>
Expand Down
4 changes: 2 additions & 2 deletions web/src/lib/components/asset-viewer/download-panel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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">
Expand Down
4 changes: 2 additions & 2 deletions web/src/lib/components/forms/create-user-form.svelte
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;

Expand All @@ -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;

$: {
Expand Down
10 changes: 5 additions & 5 deletions web/src/lib/components/forms/edit-user-form.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
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';
import { createEventDispatcher } from 'svelte';
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;
Expand All @@ -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;
Expand All @@ -42,7 +42,7 @@
email,
name,
storageLabel: storageLabel || '',
quotaSizeInBytes: quotaSize ? convertToBytes(Number(quotaSize), 'GiB') : null,
quotaSizeInBytes: quotaSize ? convertToBytes(Number(quotaSize), ByteUnit.GiB) : null,
},
});

Expand Down
8 changes: 4 additions & 4 deletions web/src/lib/components/shared-components/status-box.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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" />
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 2 additions & 2 deletions web/src/lib/utils/asset-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
37 changes: 0 additions & 37 deletions web/src/lib/utils/byte-converter.ts

This file was deleted.

25 changes: 25 additions & 0 deletions web/src/lib/utils/byte-units.spec.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
59 changes: 41 additions & 18 deletions web/src/lib/utils/byte-units.ts
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -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'];

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));
export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, ByteUnit] {
const magnitude = Math.floor(Math.log(bytes === 0 ? 1 : bytes) / Math.log(1024));

return [remainder, units[magnitude]];
return [Number.parseFloat((bytes / 1024 ** magnitude).toFixed(maxPrecision)), magnitude];
}

/**
Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions web/src/routes/admin/library-management/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading