element-portable/src/utils/FormattingUtils.ts
David Langley 491f0cd08a
Change license (#13)
* Copyright headers 1

* Licence headers 2

* Copyright Headers 3

* Copyright Headers 4

* Copyright Headers 5

* Copyright Headers 6

* Copyright headers 7

* Add copyright headers for html and config file

* Replace license files and update package.json

* Update with CLA

* lint
2024-09-09 13:57:16 +00:00

125 lines
4.3 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.
Copyright 2016 OpenMarket Ltd
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
Please see LICENSE files in the repository root for full details.
*/
import { ReactElement, ReactNode } from "react";
import { useIdColorHash } from "@vector-im/compound-web";
import { _t, getCurrentLanguage, getUserLanguage } from "../languageHandler";
import { jsxJoin } from "./ReactUtils";
const locale = getCurrentLanguage();
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
// it in every function call
const compactFormatter = new Intl.NumberFormat(locale, {
notation: "compact",
});
/**
* formats and rounds numbers to fit into ~3 characters, suitable for badge counts
* e.g: 999, 10K, 99K, 1M, 10M, 99M, 1B, 10B, ...
*/
export function formatCount(count: number): string {
return compactFormatter.format(count);
}
// It's quite costly to instanciate `Intl.NumberFormat`, hence why we do not do
// it in every function call
const formatter = new Intl.NumberFormat(locale);
/**
* Format a count showing the whole number but making it a bit more readable.
* e.g: 1000 => 1,000
*/
export function formatCountLong(count: number): string {
return formatter.format(count);
}
/**
* format a size in bytes into a human readable form
* e.g: 1024 -> 1.00 KB
*/
export function formatBytes(bytes: number, decimals = 2): string {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
}
/**
* format a key into groups of 4 characters, for easier visual inspection
*
* @param {string} key key to format
*
* @return {string}
*/
export function formatCryptoKey(key: string): string {
return key.match(/.{1,4}/g)!.join(" ");
}
export function getUserNameColorClass(userId: string): string {
// eslint-disable-next-line react-hooks/rules-of-hooks
const number = useIdColorHash(userId);
return `mx_Username_color${number}`;
}
/**
* Constructs a written English string representing `items`, with an optional
* limit on the number of items included in the result. If specified and if the
* length of `items` is greater than the limit, the string "and n others" will
* be appended onto the result. If `items` is empty, returns the empty string.
* If there is only one item, return it.
* @param {string[]} items the items to construct a string from.
* @param {number?} itemLimit the number by which to limit the list.
* @returns {string} a string constructed by joining `items` with a comma
* between each item, but with the last item appended as " and [lastItem]".
*/
export function formatList(items: string[], itemLimit?: number, includeCount?: boolean): string;
export function formatList(items: ReactElement[], itemLimit?: number, includeCount?: boolean): ReactElement;
export function formatList(items: ReactNode[], itemLimit?: number, includeCount?: boolean): ReactNode;
export function formatList(items: ReactNode[], itemLimit = items.length, includeCount = false): ReactNode {
let remaining = Math.max(items.length - itemLimit, 0);
if (items.length <= 1) {
return items[0] ?? "";
}
const formatter = new Intl.ListFormat(getUserLanguage(), { style: "long", type: "conjunction" });
if (remaining > 0) {
if (includeCount) {
itemLimit--;
remaining++;
}
items = items.slice(0, itemLimit);
let joinedItems: ReactNode;
if (items.every((e) => typeof e === "string")) {
joinedItems = items.join(", ");
} else {
joinedItems = jsxJoin(items, ", ");
}
return _t("items_and_n_others", { count: remaining }, { Items: () => joinedItems });
}
if (items.every((e) => typeof e === "string")) {
return formatter.format(items as string[]);
}
const parts = formatter.formatToParts(items.map((_, i) => `${i}`));
return jsxJoin(
parts.map((part) => {
if (part.type === "literal") return part.value;
return items[parseInt(part.value, 10)];
}),
);
}