a simple web player for subsonic
tinysub.devins.page
subsonic
navidrome
javascript
1// generic DOM utilities and element factories
2
3// generic element creator
4function createElement(tag, config = {}) {
5 const {
6 className,
7 textContent,
8 html,
9 attributes = {},
10 listeners = {},
11 children = [],
12 } = config;
13 const el = document.createElement(tag);
14 if (className) el.className = className;
15 if (textContent) el.textContent = textContent;
16 if (html) el.innerHTML = html;
17 Object.entries(attributes).forEach(([key, value]) =>
18 el.setAttribute(key, value),
19 );
20 Object.entries(listeners).forEach(([event, handler]) =>
21 el.addEventListener(event, handler),
22 );
23 children.forEach((child) => el.appendChild(child));
24 return el;
25}
26
27// create a button with icon
28function createIconButton(className, alt, iconPath, onCallback) {
29 const btn = createElement("button", {
30 className,
31 children: [createElement("img", { attributes: { src: iconPath, alt } })],
32 });
33
34 if (onCallback) {
35 btn.addEventListener("click", async (e) => {
36 e.preventDefault();
37 btn.classList.add(CLASSES.DISABLED);
38 try {
39 await onCallback();
40 } finally {
41 btn.classList.remove(CLASSES.DISABLED);
42 }
43 });
44 }
45
46 return btn;
47}
48
49// format seconds as mm:ss string
50function formatDuration(seconds) {
51 if (!seconds || !Number.isFinite(seconds)) return "0:00";
52 const minutes = Math.floor(seconds / 60);
53 return `${minutes}:${Math.floor(seconds % 60)
54 .toString()
55 .padStart(2, "0")}`;
56}