mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-08-28 13:34:33 +00:00
refactor(frontend): update download functionality and improve time formatting utilities
This commit is contained in:
@@ -1,24 +1,65 @@
|
||||
class DownloadUtils {
|
||||
static downloadFile(filename, blob) {
|
||||
// create object url for blob
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
function isAndroidSaveBridge() {
|
||||
return (
|
||||
typeof window !== "undefined" &&
|
||||
window.MeshChatXAndroid != null &&
|
||||
typeof window.MeshChatXAndroid.saveDownload === "function"
|
||||
);
|
||||
}
|
||||
|
||||
// create hidden link element to download blob
|
||||
class DownloadUtils {
|
||||
static _blobToBase64(blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
const s = reader.result;
|
||||
if (typeof s !== "string") {
|
||||
reject(new Error("readAsDataURL failed"));
|
||||
return;
|
||||
}
|
||||
const comma = s.indexOf(",");
|
||||
resolve(comma >= 0 ? s.slice(comma + 1) : s);
|
||||
};
|
||||
reader.onerror = () => reject(reader.error || new Error("read failed"));
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
static _triggerBrowserDownload(filename, objectUrl) {
|
||||
const link = document.createElement("a");
|
||||
link.href = objectUrl;
|
||||
link.download = filename;
|
||||
link.style.display = "none";
|
||||
document.body.append(link);
|
||||
|
||||
// click link to download file in browser
|
||||
link.click();
|
||||
|
||||
// link element is no longer needed
|
||||
link.remove();
|
||||
|
||||
// revoke object url to clear memory
|
||||
setTimeout(() => URL.revokeObjectURL(objectUrl), 10000);
|
||||
}
|
||||
|
||||
static downloadFromBase64(filename, fileBytesBase64) {
|
||||
if (isAndroidSaveBridge()) {
|
||||
window.MeshChatXAndroid.saveDownload(filename, fileBytesBase64);
|
||||
return;
|
||||
}
|
||||
const byteCharacters = atob(fileBytesBase64);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray]);
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
DownloadUtils._triggerBrowserDownload(filename, objectUrl);
|
||||
}
|
||||
|
||||
static async downloadFile(filename, blob) {
|
||||
if (isAndroidSaveBridge()) {
|
||||
const b64 = await DownloadUtils._blobToBase64(blob);
|
||||
window.MeshChatXAndroid.saveDownload(filename, b64);
|
||||
return;
|
||||
}
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
DownloadUtils._triggerBrowserDownload(filename, objectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadUtils;
|
||||
|
||||
@@ -46,38 +46,38 @@ class Utils {
|
||||
};
|
||||
}
|
||||
|
||||
static formatSeconds(seconds) {
|
||||
static formatSecondsWithoutAgo(seconds) {
|
||||
const parsedSeconds = this.parseSeconds(seconds);
|
||||
|
||||
if (parsedSeconds.days > 0) {
|
||||
if (parsedSeconds.days === 1) {
|
||||
return "1 day ago";
|
||||
} else {
|
||||
return parsedSeconds.days + " days ago";
|
||||
return "1 day";
|
||||
}
|
||||
return parsedSeconds.days + " days";
|
||||
}
|
||||
|
||||
if (parsedSeconds.hours > 0) {
|
||||
if (parsedSeconds.hours === 1) {
|
||||
return "1 hour ago";
|
||||
} else {
|
||||
return parsedSeconds.hours + " hours ago";
|
||||
return "1 hour";
|
||||
}
|
||||
return parsedSeconds.hours + " hours";
|
||||
}
|
||||
|
||||
if (parsedSeconds.minutes > 0) {
|
||||
if (parsedSeconds.minutes === 1) {
|
||||
return "1 min ago";
|
||||
} else {
|
||||
return parsedSeconds.minutes + " mins ago";
|
||||
return "1 min";
|
||||
}
|
||||
return parsedSeconds.minutes + " mins";
|
||||
}
|
||||
|
||||
if (parsedSeconds.seconds <= 1) {
|
||||
return "a second ago";
|
||||
} else {
|
||||
return parsedSeconds.seconds + " seconds ago";
|
||||
return "a second";
|
||||
}
|
||||
return parsedSeconds.seconds + " seconds";
|
||||
}
|
||||
|
||||
static formatSeconds(seconds) {
|
||||
return this.formatSecondsWithoutAgo(seconds) + " ago";
|
||||
}
|
||||
|
||||
static formatTimeAgo(datetimeString) {
|
||||
@@ -108,11 +108,48 @@ class Utils {
|
||||
return this.formatSeconds(diffSec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relative age fragment without trailing " ago" / "ago" duplication when used inside i18n
|
||||
* strings that already add a locale-specific suffix (e.g. "Last synced {time} ago.").
|
||||
* Sub-minute uses a neutral phrase so English templates avoid "just now ago".
|
||||
*/
|
||||
static formatTimeAgoForI18n(datetimeString) {
|
||||
if (!datetimeString) return "unknown";
|
||||
|
||||
let dateString = datetimeString;
|
||||
if (typeof dateString === "string" && !dateString.includes("Z") && !dateString.includes("+")) {
|
||||
dateString = dateString.replace(" ", "T") + "Z";
|
||||
}
|
||||
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffMs = now.getTime() - date.getTime();
|
||||
const diffSec = Math.round(diffMs / 1000);
|
||||
|
||||
if (diffSec < 60) {
|
||||
return "less than a minute";
|
||||
}
|
||||
|
||||
if (diffSec > 86400) {
|
||||
return dayjs(date).format("MMM D, h:mm A");
|
||||
}
|
||||
|
||||
return this.formatSecondsWithoutAgo(diffSec);
|
||||
}
|
||||
|
||||
static formatSecondsAgo(seconds) {
|
||||
const secondsAgo = Math.round(Date.now() / 1000 - seconds);
|
||||
return this.formatSeconds(secondsAgo);
|
||||
}
|
||||
|
||||
static formatSecondsAgoForI18n(seconds) {
|
||||
const secondsAgo = Math.round(Date.now() / 1000 - seconds);
|
||||
if (secondsAgo < 60) {
|
||||
return "less than a minute";
|
||||
}
|
||||
return this.formatSecondsWithoutAgo(secondsAgo);
|
||||
}
|
||||
|
||||
static formatMinutesSeconds(seconds) {
|
||||
const parsedSeconds = this.parseSeconds(seconds);
|
||||
const paddedMinutes = parsedSeconds.minutes.toString().padStart(2, "0");
|
||||
|
||||
Reference in New Issue
Block a user