mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-19 09:01:39 -06:00
a5b8b7052b
* refactor(ui): consolidate task and log contracts * fix(cron): preserve advanced delivery routing * refactor(ui): share relative-time bucketing * fix(ui): retain narrow cron view types * fix(ui): keep cron form field keys total * style(ui): use narrowed task result boolean
66 lines
2.4 KiB
TypeScript
66 lines
2.4 KiB
TypeScript
import { expectDefined } from "./expect.js";
|
|
|
|
type ByteSizeUnit = "byte" | "kilo" | "mega" | "giga" | "tera";
|
|
type ByteSizeStyle = "iec" | "legacy-binary";
|
|
|
|
type ByteSizeFormatOptions = {
|
|
style: ByteSizeStyle;
|
|
maxUnit: ByteSizeUnit;
|
|
separator: "" | " ";
|
|
fractionDigits: number | ((value: number, unit: ByteSizeUnit) => number | null);
|
|
floorUnits?: readonly ByteSizeUnit[];
|
|
};
|
|
|
|
const BYTE_SIZE_UNITS: readonly ByteSizeUnit[] = ["byte", "kilo", "mega", "giga", "tera"];
|
|
const BYTE_SIZE_STYLES = {
|
|
iec: { base: 1024, labels: ["B", "KiB", "MiB", "GiB", "TiB"] },
|
|
"legacy-binary": { base: 1024, labels: ["B", "KB", "MB", "GB", "TB"] },
|
|
} as const satisfies Record<ByteSizeStyle, { base: number; labels: readonly string[] }>;
|
|
|
|
export type RelativeTimeUnit = "second" | "minute" | "hour" | "day";
|
|
|
|
/** Buckets an absolute duration while preserving nested display rounding at unit boundaries. */
|
|
export function bucketRelativeTimeMs(durationMs: number): {
|
|
value: number;
|
|
unit: RelativeTimeUnit;
|
|
} {
|
|
const seconds = Math.round(durationMs / 1000);
|
|
if (seconds < 60) {
|
|
return { value: seconds, unit: "second" };
|
|
}
|
|
const minutes = Math.round(seconds / 60);
|
|
if (minutes < 60) {
|
|
return { value: minutes, unit: "minute" };
|
|
}
|
|
const hours = Math.round(minutes / 60);
|
|
return hours < 48
|
|
? { value: hours, unit: "hour" }
|
|
: { value: Math.round(hours / 24), unit: "day" };
|
|
}
|
|
|
|
/** Formats a byte count with caller-explicit scale, labels, precision, and unit cap. */
|
|
export function formatByteSize(bytes: number, options: ByteSizeFormatOptions): string {
|
|
const { base, labels } = BYTE_SIZE_STYLES[options.style];
|
|
const maxUnitIndex = BYTE_SIZE_UNITS.indexOf(options.maxUnit);
|
|
let unitIndex = 0;
|
|
let value = bytes;
|
|
while (value >= base && unitIndex < maxUnitIndex) {
|
|
value /= base;
|
|
unitIndex += 1;
|
|
}
|
|
|
|
const unit = expectDefined(BYTE_SIZE_UNITS[unitIndex], "byte-size unit");
|
|
const label = expectDefined(labels[unitIndex], "byte-size label");
|
|
const fractionDigits =
|
|
typeof options.fractionDigits === "function"
|
|
? options.fractionDigits(value, unit)
|
|
: options.fractionDigits;
|
|
if (fractionDigits === null) {
|
|
return `${value}${options.separator}${label}`;
|
|
}
|
|
if (options.floorUnits?.includes(unit)) {
|
|
value = Math.floor(value * 10 ** fractionDigits) / 10 ** fractionDigits;
|
|
}
|
|
return `${value.toFixed(fractionDigits)}${options.separator}${label}`;
|
|
}
|