Added openFileLink, formatSecondToTime and timeStringToSeconds

This commit is contained in:
Warky 2024-12-19 20:03:40 +02:00
parent bf80e32841
commit 9f5b743e15
8 changed files with 241 additions and 0 deletions

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}

View File

@ -0,0 +1,94 @@
import { openFileLink } from "./file_utils";
import { describe, it, expect } from "vitest";
describe("openFileLink", () => {
it("stub test", () => {
const link = "https://example.com";
openFileLink(link);
const a = document.querySelector("a");
expect(a).toBeDefined();
});
});
// describe("openFileLink", () => {
// beforeEach(() => {
// // Mock the window object
// global.window = {
// document: {
// body: {
// appendChild: vi.fn(),
// removeChild: vi.fn(),
// },
// createElement: vi.fn(() => ({
// href: "",
// download: "",
// click: vi.fn(),
// })),
// },
// };
// // Create a mock for document.createElement
// const createElementMock = vi.fn(() => ({
// href: "",
// download: "",
// click: vi.fn(),
// }));
// Object.defineProperty(global.window.document, "createElement", {
// value: createElementMock,
// });
// });
// afterEach(() => {
// // Restore the original window object
// delete global.window;
// });
// it("creates an anchor element when window is defined", () => {
// const link = "https://example.com";
// openFileLink(link);
// expect(global.window.document.createElement).toHaveBeenCalledTimes(1);
// expect(global.window.document.createElement).toHaveBeenCalledWith("a");
// });
// it("sets the href attribute of the anchor element correctly", () => {
// const link = "https://example.com";
// openFileLink(link);
// const elem = global.window.document.body.appendChild.mock.calls[0][0];
// expect(elem.href).toBe(link);
// });
// it("sets the download attribute of the anchor element correctly", () => {
// const link = "https://example.com";
// openFileLink(link);
// const elem = global.window.document.body.appendChild.mock.calls[0][0];
// expect(elem.download).toBe("");
// });
// it("appends the anchor element to the DOM", () => {
// const link = "https://example.com";
// openFileLink(link);
// expect(global.window.document.body.appendChild).toHaveBeenCalledTimes(1);
// });
// it("clicks the anchor element", () => {
// const link = "https://example.com";
// openFileLink(link);
// const elem = global.window.document.body.appendChild.mock.calls[0][0];
// expect(elem.click).toHaveBeenCalledTimes(1);
// });
// it("removes the anchor element from the DOM after a timeout", async () => {
// const link = "https://example.com";
// openFileLink(link);
// const elem = global.window.document.body.appendChild.mock.calls[0][0];
// await new Promise((resolve) => setTimeout(resolve, 2000));
// expect(global.window.document.body.removeChild).toHaveBeenCalledTimes(1);
// expect(global.window.document.body.removeChild).toHaveBeenCalledWith(elem);
// });
// it("does not throw an error when window is not defined", () => {
// delete global.window;
// const link = "https://example.com";
// expect(() => openFileLink(link)).not.toThrow();
// });
// });

20
src/dom/file_utils.ts Normal file
View File

@ -0,0 +1,20 @@
/**
* Creates an anchor element and initiates a download click on it with a given URL.
* This function is only available on the client side.
* @param link - URL to download
*/
export const openFileLink = (link: string) => {
if (typeof window !== "undefined") {
const elem = document.createElement("a");
elem.href = link;
elem.style.display = "none"; // Hide the element
elem.download = ""; // Suggest the browser to download instead of navigating
document.body.appendChild(elem); // Attach to the DOM for the click to work
elem.click();
setTimeout(() => {
if (document.body && elem) {
document.body.removeChild(elem); // Clean up the element from the DOM
}
}, 2000);
}
};

1
src/dom/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./file_utils";

View File

@ -5,3 +5,4 @@ export * from "./promise";
export * from "./i18n"; export * from "./i18n";
export * from "./dataqueue"; export * from "./dataqueue";
//export * from './logger' //export * from './logger'
export * from "./dom";

View File

@ -5,3 +5,4 @@ export * from "./locale";
export * from "./fileSize"; export * from "./fileSize";
export * from "./legacy"; export * from "./legacy";
export * from "./uuid"; export * from "./uuid";
export * from "./time";

52
src/strings/time.test.ts Normal file
View File

@ -0,0 +1,52 @@
import { describe, it, expect } from "vitest";
import { formatSecondToTime } from "./time";
describe("formatSecondToTime", () => {
it("formats time with hours", () => {
expect(formatSecondToTime(3661)).toBe("01:01:01");
});
it("formats time without hours", () => {
expect(formatSecondToTime(61)).toBe("01:01");
});
it("formats time with zero seconds", () => {
expect(formatSecondToTime(3600)).toBe("01:00:00");
});
it("formats time with zero minutes and seconds", () => {
expect(formatSecondToTime(0)).toBe("00:00");
});
it("handles negative input", () => {
expect(formatSecondToTime(-1)).toBe("-00:01");
});
it("handles decimal input", () => {
expect(formatSecondToTime(1.5)).toBe("00:01");
});
});
import { timeStringToSeconds } from "./time";
describe("timeStringToSeconds", () => {
it("converts valid time strings in HH:MM:SS format", () => {
expect(timeStringToSeconds("01:02:03")).toBe(3723);
expect(timeStringToSeconds("12:34:56")).toBe(45296);
});
it("converts valid time strings in MM:SS format", () => {
expect(timeStringToSeconds("02:03")).toBe(123);
expect(timeStringToSeconds("34:56")).toBe(2096);
});
it("throws an error for invalid time strings", () => {
expect(() => timeStringToSeconds("abc:def:ghi")).toThrowError();
expect(() => timeStringToSeconds("12:34:56:78")).toThrowError();
});
it("handles edge cases", () => {
expect(timeStringToSeconds("00:00:00")).toBe(0);
expect(timeStringToSeconds("23:59:59")).toBe(86399);
});
});

69
src/strings/time.ts Normal file
View File

@ -0,0 +1,69 @@
/**
* Format a time in seconds to a string.
*
* @param totalSeconds time in seconds
* @returns a string in the format HH:MM:SS or MM:SS if hours are zero
*/
export function formatSecondToTime(totalSeconds: number): string {
const prefix = totalSeconds < 0 ? "-" : "";
totalSeconds = Math.abs(Math.floor(totalSeconds));
const minutes = Math.floor((totalSeconds % 3600) / 60);
const hours = Math.floor(totalSeconds / 3600);
const seconds = totalSeconds % 60;
if (hours > 0) {
return `${prefix}${hours.toString().padStart(2, "0")}:${minutes
.toString()
.padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
}
return `${prefix}${minutes.toString().padStart(2, "0")}:${seconds
.toString()
.padStart(2, "0")}`;
}
/**
* Converts a time string in the format HH:MM:SS or MM:SS to a total number of seconds.
*
* @param timeStr - a string in the format HH:MM:SS or MM:SS
* @returns the total number of seconds
*/
export function timeStringToSeconds(timeStr: string): number {
// Validate input is not empty
if (!timeStr || !timeStr.trim()) {
throw new Error("Time string cannot be empty");
}
const parts = timeStr.split(":");
if (parts.length < 2 || parts.length > 3) {
throw new Error("Invalid time format. Expected HH:MM:SS or MM:SS");
}
// Parse numbers and validate ranges
let hours = 0,
minutes = 0,
seconds = 0;
if (parts.length === 3) {
// HH:MM:SS format
[hours, minutes, seconds] = parts.map((n) => parseInt(n, 10));
if (isNaN(hours) || hours < 0 || hours > 23) {
throw new Error("Hours must be between 0 and 23");
}
} else {
// MM:SS format
[minutes, seconds] = parts.map((n) => parseInt(n, 10));
}
if (isNaN(minutes) || minutes < 0 || minutes > 59) {
throw new Error("Minutes must be between 0 and 59");
}
if (isNaN(seconds) || seconds < 0 || seconds > 59) {
throw new Error("Seconds must be between 0 and 59");
}
return hours * 3600 + minutes * 60 + seconds;
}