diff --git a/app/components/form/fields/ImageSelectField.tsx b/app/components/form/fields/ImageSelectField.tsx index db2ff1f559..8d9a2f8021 100644 --- a/app/components/form/fields/ImageSelectField.tsx +++ b/app/components/form/fields/ImageSelectField.tsx @@ -9,7 +9,7 @@ import { useController, type Control } from 'react-hook-form' import type { Image } from '@oxide/api' import type { ListboxItem } from '@oxide/ui' -import { GiB } from '@oxide/util' +import { bytesToGiB, GiB } from '@oxide/util' import type { InstanceCreateInput } from 'app/forms/instance-create' @@ -41,22 +41,43 @@ export function ImageSelectField({ images, control }: ImageSelectFieldProps) { ) } -const Slash = () => / +const Slash = () => ( + / +) export function toListboxItem(i: Image, includeProjectSiloIndicator = false): ListboxItem { - const projectSiloIndicator = includeProjectSiloIndicator ? ( - <> - {i.projectId ? 'Project image' : 'Silo image'} - - ) : null + const { name, os, projectId, size, version } = i + const formattedSize = `${bytesToGiB(size, 1)} GiB` + + // filter out any undefined metadata and create a comma-separated list + // for the selected listbox item (shown in labelString) + const condensedImageMetadata = [os, version, formattedSize].filter((i) => !!i).join(', ') + const metadataForLabelString = condensedImageMetadata.length + ? ` (${condensedImageMetadata})` + : '' + + // for metadata showing in the dropdown's options, include the project / silo indicator if requested + const projectSiloIndicator = includeProjectSiloIndicator + ? `${projectId ? 'Project' : 'Silo'} image` + : null + // filter out undefined metadata here, as well, and create a ``-separated list + // for the listbox item (shown for each item in the dropdown) + const metadataForLabel = [os, version, formattedSize, projectSiloIndicator] + .filter((i) => !!i) + .map((i, index) => ( + + {index > 0 ? : ''} + {i} + + )) return { value: i.id, - labelString: `${i.name} (${i.os}, ${i.version})`, + labelString: `${name}${metadataForLabelString}`, label: ( <> -
{i.name}
-
- {i.os} {i.version} {projectSiloIndicator} +
{name}
+
+ {metadataForLabel}
), diff --git a/libs/util/math.spec.ts b/libs/util/math.spec.ts index ee2fd81962..19c6f2b4d0 100644 --- a/libs/util/math.spec.ts +++ b/libs/util/math.spec.ts @@ -7,7 +7,20 @@ */ import { expect, it } from 'vitest' -import { splitDecimal } from './math' +import { GiB } from '.' +import { round, splitDecimal } from './math' + +it('rounds properly', () => { + expect(round(123.456, 0)).toEqual(123) + expect(round(123.456, 1)).toEqual(123.5) + expect(round(123.456, 2)).toEqual(123.46) + expect(round(123.456, 3)).toEqual(123.456) + expect(round(123.456, 4)).toEqual(123.456) // trailing zeros are culled + expect(round(1.9, 0)).toEqual(2) + expect(round(1.9, 1)).toEqual(1.9) + expect(round(5 / 2, 2)).toEqual(2.5) // math expressions are resolved + expect(round(1879048192 / GiB, 2)).toEqual(1.75) // constants can be evaluated +}) it.each([ [1.23, ['1', '.23']], diff --git a/libs/util/units.ts b/libs/util/units.ts index 9ccdc1aab3..e88a5672a5 100644 --- a/libs/util/units.ts +++ b/libs/util/units.ts @@ -12,5 +12,5 @@ export const MiB = 1024 * KiB export const GiB = 1024 * MiB export const TiB = 1024 * GiB -export const bytesToGiB = (b: number) => round(b / GiB, 2) -export const bytesToTiB = (b: number) => round(b / TiB, 2) +export const bytesToGiB = (b: number, digits = 2) => round(b / GiB, digits) +export const bytesToTiB = (b: number, digits = 2) => round(b / TiB, digits)