2022-01-20 18:27:56 +00:00
|
|
|
/* @license This file Copyright (C) 2020-2022 Mnemosyne LLC.
|
2022-02-07 16:25:02 +00:00
|
|
|
It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
2022-01-20 18:27:56 +00:00
|
|
|
or any future license endorsed by Mnemosyne LLC.
|
|
|
|
License text can be found in the licenses/ folder. */
|
2020-10-24 01:04:25 +00:00
|
|
|
|
|
|
|
const plural_rules = new Intl.PluralRules();
|
|
|
|
const current_locale = plural_rules.resolvedOptions().locale;
|
|
|
|
const number_format = new Intl.NumberFormat(current_locale);
|
|
|
|
|
|
|
|
const kilo = 1000;
|
|
|
|
const mem_formatters = [
|
2022-02-24 19:27:32 +00:00
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 0,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'byte',
|
|
|
|
}),
|
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 0,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'kilobyte',
|
|
|
|
}),
|
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 0,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'megabyte',
|
|
|
|
}),
|
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'gigabyte',
|
|
|
|
}),
|
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'terabyte',
|
|
|
|
}),
|
|
|
|
new Intl.NumberFormat(current_locale, {
|
|
|
|
maximumFractionDigits: 2,
|
|
|
|
style: 'unit',
|
|
|
|
unit: 'petabyte',
|
|
|
|
}),
|
2020-10-24 01:04:25 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
const fmt_kBps = new Intl.NumberFormat(current_locale, {
|
2022-02-24 19:27:32 +00:00
|
|
|
maximumFractionDigits: 2,
|
2020-10-24 01:04:25 +00:00
|
|
|
style: 'unit',
|
|
|
|
unit: 'kilobyte-per-second',
|
|
|
|
});
|
|
|
|
const fmt_MBps = new Intl.NumberFormat(current_locale, {
|
2022-02-24 19:27:32 +00:00
|
|
|
maximumFractionDigits: 2,
|
2020-10-24 01:04:25 +00:00
|
|
|
style: 'unit',
|
|
|
|
unit: 'megabyte-per-second',
|
|
|
|
});
|
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
export const Formatter = {
|
|
|
|
/** Round a string of a number to a specified number of decimal places */
|
|
|
|
_toTruncFixed(number, places) {
|
|
|
|
const returnValue = Math.floor(number * 10 ** places) / 10 ** places;
|
|
|
|
return returnValue.toFixed(places);
|
|
|
|
},
|
|
|
|
|
|
|
|
countString(msgid, msgid_plural, n) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return `${this.number(n)} ${this.ngettext(msgid, msgid_plural, n)}`;
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2022-10-13 14:35:10 +00:00
|
|
|
// Formats a memory size into a human-readable string
|
2020-10-24 01:04:25 +00:00
|
|
|
// @param {Number} bytes the filesize in bytes
|
|
|
|
// @return {String} human-readable string
|
2021-05-19 13:43:46 +00:00
|
|
|
mem(bytes) {
|
2020-10-24 01:04:25 +00:00
|
|
|
if (bytes < 0) {
|
|
|
|
return 'Unknown';
|
|
|
|
}
|
|
|
|
if (bytes === 0) {
|
|
|
|
return 'None';
|
|
|
|
}
|
|
|
|
|
|
|
|
let size = bytes;
|
|
|
|
for (const nf of mem_formatters) {
|
|
|
|
if (size < kilo) {
|
2022-02-24 19:27:32 +00:00
|
|
|
return nf.format(size);
|
2020-10-24 01:04:25 +00:00
|
|
|
}
|
|
|
|
size /= kilo;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'E2BIG';
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
ngettext(msgid, msgid_plural, n) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return plural_rules.select(n) === 'one' ? msgid : msgid_plural;
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
number(number) {
|
|
|
|
return number_format.format(number);
|
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
|
|
|
// format a percentage to a string
|
2021-05-19 13:43:46 +00:00
|
|
|
percentString(x) {
|
2020-10-24 01:04:25 +00:00
|
|
|
const decimal_places = x < 100 ? 1 : 0;
|
|
|
|
return this._toTruncFixed(x, decimal_places);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Format a ratio to a string
|
|
|
|
*/
|
2021-05-19 13:43:46 +00:00
|
|
|
ratioString(x) {
|
2020-10-24 01:04:25 +00:00
|
|
|
if (x === -1) {
|
|
|
|
return 'None';
|
|
|
|
}
|
|
|
|
if (x === -2) {
|
|
|
|
return '∞';
|
|
|
|
}
|
|
|
|
return this.percentString(x);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
|
|
|
/**
|
2022-10-13 14:35:10 +00:00
|
|
|
* Formats a disk capacity or file size into a human-readable string
|
2020-10-24 01:04:25 +00:00
|
|
|
* @param {Number} bytes the filesize in bytes
|
|
|
|
* @return {String} human-readable string
|
|
|
|
*/
|
2021-05-19 13:43:46 +00:00
|
|
|
size(bytes) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return this.mem(bytes);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
speed(KBps) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return KBps < 999.95 ? fmt_kBps.format(KBps) : fmt_MBps.format(KBps / 1000);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
speedBps(Bps) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return this.speed(this.toKBps(Bps));
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
timeInterval(seconds) {
|
|
|
|
const days = Math.floor(seconds / 86_400);
|
2020-10-24 01:04:25 +00:00
|
|
|
if (days) {
|
|
|
|
return this.countString('day', 'days', days);
|
|
|
|
}
|
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
const hours = Math.floor((seconds % 86_400) / 3600);
|
2020-10-24 01:04:25 +00:00
|
|
|
if (hours) {
|
|
|
|
return this.countString('hour', 'hours', hours);
|
|
|
|
}
|
|
|
|
|
|
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
if (minutes) {
|
|
|
|
return this.countString('minute', 'minutes', minutes);
|
|
|
|
}
|
|
|
|
|
|
|
|
seconds = Math.floor(seconds % 60);
|
|
|
|
return this.countString('second', 'seconds', seconds);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
timestamp(seconds) {
|
2020-10-24 01:04:25 +00:00
|
|
|
if (!seconds) {
|
|
|
|
return 'N/A';
|
|
|
|
}
|
|
|
|
|
|
|
|
const myDate = new Date(seconds * 1000);
|
|
|
|
const now = new Date();
|
|
|
|
|
|
|
|
let date = '';
|
|
|
|
let time = '';
|
|
|
|
|
|
|
|
const sameYear = now.getFullYear() === myDate.getFullYear();
|
|
|
|
const sameMonth = now.getMonth() === myDate.getMonth();
|
|
|
|
|
|
|
|
const dateDiff = now.getDate() - myDate.getDate();
|
|
|
|
if (sameYear && sameMonth && Math.abs(dateDiff) <= 1) {
|
|
|
|
if (dateDiff === 0) {
|
|
|
|
date = 'Today';
|
|
|
|
} else if (dateDiff === 1) {
|
|
|
|
date = 'Yesterday';
|
|
|
|
} else {
|
|
|
|
date = 'Tomorrow';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
date = myDate.toDateString();
|
|
|
|
}
|
|
|
|
|
|
|
|
let hours = myDate.getHours();
|
|
|
|
let period = 'AM';
|
|
|
|
if (hours > 12) {
|
|
|
|
hours = hours - 12;
|
|
|
|
period = 'PM';
|
|
|
|
}
|
|
|
|
if (hours === 0) {
|
|
|
|
hours = 12;
|
|
|
|
}
|
|
|
|
if (hours < 10) {
|
|
|
|
hours = `0${hours}`;
|
|
|
|
}
|
|
|
|
let minutes = myDate.getMinutes();
|
|
|
|
if (minutes < 10) {
|
|
|
|
minutes = `0${minutes}`;
|
|
|
|
}
|
|
|
|
seconds = myDate.getSeconds();
|
|
|
|
if (seconds < 10) {
|
|
|
|
seconds = `0${seconds}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
time = [hours, minutes, seconds].join(':');
|
|
|
|
|
|
|
|
return [date, time, period].join(' ');
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
2020-10-24 01:04:25 +00:00
|
|
|
|
2021-05-19 13:43:46 +00:00
|
|
|
toKBps(Bps) {
|
2020-10-24 01:04:25 +00:00
|
|
|
return Math.floor(Bps / kilo);
|
2021-05-19 13:43:46 +00:00
|
|
|
},
|
|
|
|
};
|