Skip to content

Commit 3112801

Browse files
committed
Nit: De-dupe colorSize helper, document <Size>
Wrap-up cleanup after the size-coloring pass. - The "wrap a formatted size string in a colored span" helper had four near-identical copies: `colorSize` in `lib/ai/ai-state.svelte.ts`, `lib/file-operations/transfer/transfer-error-messages.ts`, and `lib/settings/sections/AiLocalSection.svelte`, plus `colorizeFormattedSize` in `lib/file-explorer/views/full-list-utils.ts`. Extracted to one exported `colorizeSizeString(text)` in `selection-info-utils.ts`, alongside the existing `formatSizeHtmlColored(bytes, format)`. Tier resolution still goes through `tierClassForUnit`. - `lib/ui/CLAUDE.md`: added `Size.svelte` to the components table and a short section documenting when to reach for `<Size bytes={…} />` vs. `formatSizeHtmlColored()` vs. `colorizeSizeString()`. The file-list size column keeps its inline `formatSizeForDisplay` usage — noted why.
1 parent 357c4fa commit 3112801

6 files changed

Lines changed: 46 additions & 43 deletions

File tree

apps/desktop/src/lib/ai/ai-state.svelte.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ import {
1414
} from '$lib/tauri-commands'
1515
import { getSetting, setSetting } from '$lib/settings'
1616
import { loadSettings } from '$lib/settings-store'
17-
import { tierClassForUnit } from '$lib/file-explorer/selection/selection-info-utils'
18-
19-
/** Wraps a formatted size string (e.g. "1.0 GB") in a colored span for HTML embedding. */
20-
function colorSize(text: string): string {
21-
const spaceIndex = text.lastIndexOf(' ')
22-
const unit = spaceIndex >= 0 ? text.slice(spaceIndex + 1) : ''
23-
return `<span class="${tierClassForUnit(unit)}">${text}</span>`
24-
}
17+
import { colorizeSizeString } from '$lib/file-explorer/selection/selection-info-utils'
2518

2619
type AiNotificationState = 'hidden' | 'offer' | 'downloading' | 'installing' | 'ready' | 'starting'
2720

@@ -214,9 +207,9 @@ export function notifyAiOnboardingComplete(): void {
214207
function formatProgressText(progress: AiDownloadProgress): string {
215208
if (progress.totalBytes === 0) return 'Starting download...'
216209
const percent = Math.round((progress.bytesDownloaded / progress.totalBytes) * 100)
217-
const downloaded = colorSize(formatBytes(progress.bytesDownloaded))
218-
const total = colorSize(formatBytes(progress.totalBytes))
219-
const speed = colorSize(formatBytes(progress.speed))
210+
const downloaded = colorizeSizeString(formatBytes(progress.bytesDownloaded))
211+
const total = colorizeSizeString(formatBytes(progress.totalBytes))
212+
const speed = colorizeSizeString(formatBytes(progress.speed))
220213
const eta = progress.etaSeconds > 0 ? formatDuration(progress.etaSeconds) : ''
221214
const etaPart = eta ? ` — ${eta} remaining` : ''
222215
return `${String(percent)}% — ${downloaded} / ${total}${speed}/s${etaPart}`

apps/desktop/src/lib/file-explorer/selection/selection-info-utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,17 @@ export function formatSizeHtmlColored(bytes: number, format: FileSizeFormat): st
8484
.join('')
8585
}
8686

87+
/**
88+
* Wraps an already-formatted size string (e.g. `"1.02 MB"`, `"512 bytes"`) in a colored span
89+
* based on its unit suffix. Use when the value comes from a foreign formatter (like the legacy
90+
* `formatBytes` in `tauri-commands`) and you just need tier coloring on top, without re-formatting.
91+
*/
92+
export function colorizeSizeString(text: string): string {
93+
const spaceIndex = text.lastIndexOf(' ')
94+
const unit = spaceIndex >= 0 ? text.slice(spaceIndex + 1) : ''
95+
return `<span class="${tierClassForUnit(unit)}">${text}</span>`
96+
}
97+
8798
/** Formats timestamp as YYYY-MM-DD hh:mm:ss */
8899
export function formatDate(timestamp: number | null | undefined): string {
89100
if (timestamp == null) return ''

apps/desktop/src/lib/file-explorer/views/full-list-utils.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { getSetting } from '$lib/settings/settings-store'
77
import { getEffectiveScale, onDebouncedScaleChange } from '$lib/text-size.svelte'
88
import type { FileEntry } from '../types'
9-
import { formatSizeTriads, tierClassForUnit } from '../selection/selection-info-utils'
9+
import { colorizeSizeString, formatSizeTriads } from '../selection/selection-info-utils'
1010

1111
/** Layout constants for Full mode */
1212
export const FULL_LIST_ROW_HEIGHT = 20
@@ -250,16 +250,9 @@ function formatBytesHtml(bytes: number): string {
250250
.join('')
251251
}
252252

253-
/** Wraps a human-friendly size string (e.g. "1.02 MB") in a colored span based on its unit suffix. */
254-
function colorizeFormattedSize(text: string): string {
255-
const spaceIndex = text.lastIndexOf(' ')
256-
const unit = spaceIndex >= 0 ? text.slice(spaceIndex + 1) : ''
257-
return `<span class="${tierClassForUnit(unit)}">${text}</span>`
258-
}
259-
260253
/** Formats a single size line: "Label: 1.23 GB (1 234 567 890 bytes)" with colored triads and a colored unit-tagged value. */
261254
function sizeLineHtml(label: string, bytes: number, formatSize: (b: number) => string): string {
262-
return `${label}: ${colorizeFormattedSize(formatSize(bytes))} (${formatBytesHtml(bytes)} bytes)`
255+
return `${label}: ${colorizeSizeString(formatSize(bytes))} (${formatBytesHtml(bytes)} bytes)`
263256
}
264257

265258
/**
@@ -280,7 +273,7 @@ export function buildFileSizeTooltip(
280273
}
281274
const size = logical ?? physical
282275
if (size == null) return ''
283-
return { html: `${colorizeFormattedSize(formatSize(size))} (${formatBytesHtml(size)} bytes)` }
276+
return { html: `${colorizeSizeString(formatSize(size))} (${formatBytesHtml(size)} bytes)` }
284277
}
285278

286279
/**
@@ -297,7 +290,7 @@ export function buildSelectionSizeTooltip(
297290
if (totalLogical <= 0) return undefined
298291

299292
const selLine = (label: string, bytes: number) =>
300-
`${label}: ${colorizeFormattedSize(formatSize(bytes))} (${formatBytesHtml(bytes)} bytes)`
293+
`${label}: ${colorizeSizeString(formatSize(bytes))} (${formatBytesHtml(bytes)} bytes)`
301294
const lines: string[] = [selLine('Selected', selectedLogical), selLine('Of total', totalLogical)]
302295

303296
if (totalPhysical > 0) {
@@ -367,7 +360,7 @@ export function buildDirSizeTooltip(
367360
lines.push(sizeLineHtml('Content', recursiveSize, formatSize))
368361
lines.push(sizeLineHtml('On disk', recursivePhysicalSize, formatSize))
369362
} else {
370-
lines.push(`${colorizeFormattedSize(formatSize(recursiveSize))} (${formatBytesHtml(recursiveSize)} bytes)`)
363+
lines.push(`${colorizeSizeString(formatSize(recursiveSize))} (${formatBytesHtml(recursiveSize)} bytes)`)
371364
}
372365

373366
// File/folder counts with "no" for zero

apps/desktop/src/lib/file-operations/transfer/transfer-error-messages.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@
1010
import type { WriteOperationError, TransferOperationType } from '$lib/file-explorer/types'
1111
import { formatBytes } from '$lib/tauri-commands'
1212
import { isMacOS } from '$lib/shortcuts/key-capture'
13-
import { tierClassForUnit } from '$lib/file-explorer/selection/selection-info-utils'
13+
import { colorizeSizeString } from '$lib/file-explorer/selection/selection-info-utils'
1414
import { escapeHtml } from '$lib/tooltip/tooltip'
1515

16-
/** Wraps a formatted size string (e.g. "1.0 GB") in a colored span for HTML embedding. */
17-
function colorSize(text: string): string {
18-
const spaceIndex = text.lastIndexOf(' ')
19-
const unit = spaceIndex >= 0 ? text.slice(spaceIndex + 1) : ''
20-
return `<span class="${tierClassForUnit(unit)}">${text}</span>`
21-
}
22-
2316
export interface FriendlyErrorMessage {
2417
/** Short title for the error */
2518
title: string
@@ -150,7 +143,7 @@ export function getUserFriendlyMessage(
150143
case 'insufficient_space':
151144
return {
152145
title: 'Not enough space',
153-
message: `The destination needs ${colorSize(formatBytes(error.required))} but only has ${colorSize(formatBytes(error.available))} available.`,
146+
message: `The destination needs ${colorizeSizeString(formatBytes(error.required))} but only has ${colorizeSizeString(formatBytes(error.available))} available.`,
154147
suggestion:
155148
'Free up some space on the destination by deleting unnecessary files, or choose a different location.',
156149
}

apps/desktop/src/lib/settings/sections/AiLocalSection.svelte

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,7 @@
2222
} from '$lib/tauri-commands'
2323
import { computeGaugeSegments } from './ram-gauge-utils'
2424
import { getAppLogger } from '$lib/logging/logger'
25-
import { tierClassForUnit } from '$lib/file-explorer/selection/selection-info-utils'
26-
27-
/** Wraps a formatted size string (e.g. "1.0 GB") in a colored span for HTML embedding. */
28-
function colorSize(text: string): string {
29-
const spaceIndex = text.lastIndexOf(' ')
30-
const unit = spaceIndex >= 0 ? text.slice(spaceIndex + 1) : ''
31-
return `<span class="${tierClassForUnit(unit)}">${text}</span>`
32-
}
25+
import { colorizeSizeString } from '$lib/file-explorer/selection/selection-info-utils'
3326
3427
interface Props {
3528
searchQuery: string
@@ -299,9 +292,9 @@
299292
const downloadProgressText = $derived.by(() => {
300293
if (!downloadProgress) return ''
301294
if (downloadProgress.totalBytes === 0) return 'Starting download...'
302-
const downloaded = colorSize(formatBytes(downloadProgress.bytesDownloaded))
303-
const total = colorSize(formatBytes(downloadProgress.totalBytes))
304-
const speed = colorSize(formatBytes(downloadProgress.speed))
295+
const downloaded = colorizeSizeString(formatBytes(downloadProgress.bytesDownloaded))
296+
const total = colorizeSizeString(formatBytes(downloadProgress.totalBytes))
297+
const speed = colorizeSizeString(formatBytes(downloadProgress.speed))
305298
const eta = formatEta(downloadProgress.etaSeconds)
306299
const parts = [`${String(downloadPercent)}%`, `${downloaded} / ${total}`, `${speed}/s`]
307300
if (eta) parts.push(eta)

apps/desktop/src/lib/ui/CLAUDE.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Reusable UI components used across the entire desktop app.
1515
| `AlertDialog.svelte` | Single-action confirmation dialog built on `ModalDialog` |
1616
| `ProgressBar.svelte` | Reusable progress bar (just the bar, no labels or layout) |
1717
| `ProgressOverlay.svelte` | Floating top-right progress indicator: spinner, progress bar, ETA |
18+
| `Size.svelte` | Canonical inline byte-count renderer — human-friendly + rainbow tier color |
1819
| `toast/` | Centralized toast notification system — store, container, item |
1920

2021
## ModalDialog
@@ -180,6 +181,25 @@ Call `dismissTransientToasts()` on pane navigation to clear stale feedback.
180181
single `command` string prop. Handles clipboard internally (`copyToClipboard` with `navigator.clipboard` fallback).
181182
Parent controls spacing via its own wrapper. Used in `PtpcameradDialog`, `MtpPermissionDialog`, and `ShareBrowser`.
182183

184+
## Size
185+
186+
`Size.svelte` — canonical inline byte-count renderer. Takes `bytes: number | null | undefined` and optional `fallback`
187+
(default `''`). Always human-friendly (`"1.02 MB"`), always colored with the active rainbow tier class
188+
(`size-bytes`/`size-kb`/`size-mb`/`size-gb`/`size-tb`). Respects the `appearance.fileSizeFormat` setting (binary vs.
189+
decimal) and follows palette swaps via the `data-size-colors` attribute on `<html>` automatically.
190+
191+
Use this in Svelte templates: `<Size bytes={entry.size} />`. For HTML string contexts (tooltips, error messages, prose
192+
that goes through `{@html}`), use one of two helpers from `$lib/file-explorer/selection/selection-info-utils.ts`:
193+
194+
- `formatSizeHtmlColored(bytes, format)` — full pipeline: byte count → formatted string → colored span.
195+
- `colorizeSizeString(text)` — when you already have a formatted size string (e.g. from the legacy `formatBytes` in
196+
`$lib/tauri-commands`) and just need to wrap it in the right tier span.
197+
198+
The `<Size>` component does NOT cover the raw-bytes triad mode used by the file-list size column (which is gated by
199+
`listing.humanFriendlySizeUnits`). That column renders `formatSizeForDisplay` directly because it also needs the
200+
mismatch-warning + cursor-row neutralization treatment. Outside the size column, the human-friendly form is always
201+
correct — David tested every site and confirmed the simplification.
202+
183203
## Ark UI
184204

185205
Uses `@ark-ui/svelte` as the headless component library for complex interactive components (Dialog, Tabs, Select,

0 commit comments

Comments
 (0)