multi player / per user playing and translation
This commit is contained in:
+80
-26
@@ -1,5 +1,6 @@
|
||||
const elements = {
|
||||
renderer: document.querySelector("#renderer"),
|
||||
language: document.querySelector("#language"),
|
||||
discover: document.querySelector("#discover"),
|
||||
previous: document.querySelector("#previous"),
|
||||
play: document.querySelector("#play"),
|
||||
@@ -9,6 +10,8 @@ const elements = {
|
||||
position: document.querySelector("#position"),
|
||||
duration: document.querySelector("#duration"),
|
||||
repeat: document.querySelector("#repeat"),
|
||||
volumeControl: document.querySelector("#volume-control"),
|
||||
volumeToggle: document.querySelector("#volume-toggle"),
|
||||
volume: document.querySelector("#volume"),
|
||||
volumeValue: document.querySelector("#volume-value"),
|
||||
library: document.querySelector("#library"),
|
||||
@@ -41,6 +44,8 @@ const elements = {
|
||||
loginSubmit: document.querySelector("#login-submit"),
|
||||
};
|
||||
|
||||
const { t } = window.RktTranslate;
|
||||
|
||||
let state = null;
|
||||
let seekBusy = false;
|
||||
let draggedTrack = null;
|
||||
@@ -102,9 +107,16 @@ async function refreshAuth() {
|
||||
try {
|
||||
const auth = await api("/api/auth/status");
|
||||
elements.logout.hidden = !auth.enabled || !auth.authenticated;
|
||||
if (auth.enabled && !auth.authenticated) showLogin();
|
||||
if (auth.enabled && !auth.authenticated) {
|
||||
showLogin();
|
||||
} else {
|
||||
const preferences = await api("/api/preferences");
|
||||
if (window.RktTranslate.supported.includes(preferences.language)) {
|
||||
window.RktTranslate.setLanguage(preferences.language);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setStatus(`Authenticatiestatus onbekend: ${error.message}`);
|
||||
setStatus(t("authUnknown", { message: error.message }));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +148,7 @@ function replaceSelect(select, items, selectedId, signature) {
|
||||
function renderSelectors(nextState) {
|
||||
const rendererItems = nextState.renderers.map((item) => ({
|
||||
id: item.id,
|
||||
label: `${item.name} · ${item.kind.toUpperCase()}`,
|
||||
label: `${item.name} · ${item.kind === "local" ? t("rendererLocal") : item.kind.toUpperCase()}`,
|
||||
}));
|
||||
replaceSelect(
|
||||
elements.renderer,
|
||||
@@ -158,6 +170,7 @@ function renderSelectors(nextState) {
|
||||
|
||||
elements.discover.classList.toggle("busy", nextState.discovering);
|
||||
elements.discover.disabled = nextState.discovering;
|
||||
elements.renderer.disabled = nextState.renderers.length === 0;
|
||||
elements.library.disabled = nextState.libraries.length === 0;
|
||||
}
|
||||
|
||||
@@ -178,7 +191,7 @@ function entryAction(label, title, handler) {
|
||||
function renderBrowser(nextState) {
|
||||
const selectedLibrary = nextState.libraries.find((item) => item.id === nextState.libraryId);
|
||||
const path = [selectedLibrary?.name, ...nextState.browser.path].filter(Boolean);
|
||||
elements.breadcrumb.textContent = path.length ? path.join(" / ") : "Geen bibliotheek";
|
||||
elements.breadcrumb.textContent = path.length ? path.join(" / ") : t("noLibrary");
|
||||
elements.breadcrumb.title = elements.breadcrumb.textContent;
|
||||
elements.libraryUp.disabled = !nextState.browser.canGoUp;
|
||||
elements.libraryEmpty.hidden = nextState.libraries.length > 0;
|
||||
@@ -205,11 +218,11 @@ function renderBrowser(nextState) {
|
||||
const actions = document.createElement("span");
|
||||
actions.className = "entry-actions";
|
||||
actions.append(
|
||||
entryAction("▶", `${entry.name} nu afspelen`, () => {
|
||||
command("item-play", { index: entry.index }, `${entry.name} laden…`);
|
||||
entryAction("▶", t("playNow", { name: entry.name }), () => {
|
||||
command("item-play", { index: entry.index }, t("loadingNamed", { name: entry.name }));
|
||||
}),
|
||||
entryAction("+", `${entry.name} toevoegen`, () => {
|
||||
command("item-add", { index: entry.index }, `${entry.name} toevoegen…`);
|
||||
entryAction("+", t("addNamed", { name: entry.name }), () => {
|
||||
command("item-add", { index: entry.index }, t("addingNamed", { name: entry.name }));
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -218,7 +231,7 @@ function renderBrowser(nextState) {
|
||||
row.addEventListener("click", () => command("browse", { index: entry.index }));
|
||||
} else {
|
||||
row.addEventListener("dblclick", () => {
|
||||
command("item-play", { index: entry.index }, `${entry.name} laden…`);
|
||||
command("item-play", { index: entry.index }, t("loadingNamed", { name: entry.name }));
|
||||
});
|
||||
}
|
||||
row.addEventListener("keydown", (event) => {
|
||||
@@ -226,10 +239,10 @@ function renderBrowser(nextState) {
|
||||
command(
|
||||
entry.kind === "container" ? "browse" : "item-play",
|
||||
{ index: entry.index },
|
||||
entry.kind === "track" ? `${entry.name} laden…` : "",
|
||||
entry.kind === "track" ? t("loadingNamed", { name: entry.name }) : "",
|
||||
);
|
||||
} else if (event.key === "+") {
|
||||
command("item-add", { index: entry.index }, `${entry.name} toevoegen…`);
|
||||
command("item-add", { index: entry.index }, t("addingNamed", { name: entry.name }));
|
||||
}
|
||||
});
|
||||
return row;
|
||||
@@ -253,7 +266,7 @@ function renderTabs(nextState) {
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.className = "tab-name";
|
||||
name.textContent = tab.name;
|
||||
name.textContent = tab.name === "Default" ? t("defaultPlaylist") : tab.name;
|
||||
const count = document.createElement("span");
|
||||
count.className = "tab-count";
|
||||
count.textContent = tab.count;
|
||||
@@ -261,7 +274,7 @@ function renderTabs(nextState) {
|
||||
|
||||
button.addEventListener("click", () => command("tab-select", { index: tab.index }));
|
||||
button.addEventListener("dblclick", () => {
|
||||
const newName = window.prompt("Naam van de afspeellijst", tab.name);
|
||||
const newName = window.prompt(t("playlistName"), tab.name);
|
||||
if (newName !== null) command("tab-rename", { index: tab.index, name: newName });
|
||||
});
|
||||
|
||||
@@ -269,7 +282,7 @@ function renderTabs(nextState) {
|
||||
const remove = document.createElement("span");
|
||||
remove.className = "tab-delete";
|
||||
remove.textContent = "×";
|
||||
remove.title = "Afspeellijst verwijderen";
|
||||
remove.title = t("removePlaylist");
|
||||
remove.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
command("tab-delete", { index: tab.index });
|
||||
@@ -328,8 +341,8 @@ function renderPlaylist(nextState) {
|
||||
remove.className = "row-action";
|
||||
remove.type = "button";
|
||||
remove.textContent = "×";
|
||||
remove.title = `${track.title} verwijderen`;
|
||||
remove.setAttribute("aria-label", `${track.title} verwijderen`);
|
||||
remove.title = t("removeNamed", { name: track.title });
|
||||
remove.setAttribute("aria-label", t("removeNamed", { name: track.title }));
|
||||
remove.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
command("track-remove", { index: track.index });
|
||||
@@ -366,7 +379,7 @@ function renderPlaylist(nextState) {
|
||||
}
|
||||
|
||||
elements.playlistEmpty.hidden = nextState.tracks.length > 0;
|
||||
elements.count.textContent = `${nextState.tracks.length} ${nextState.tracks.length === 1 ? "track" : "tracks"}`;
|
||||
elements.count.textContent = `${nextState.tracks.length} ${t(nextState.tracks.length === 1 ? "oneTrack" : "manyTracks")}`;
|
||||
elements.playlistClear.disabled = nextState.tracks.length === 0;
|
||||
}
|
||||
|
||||
@@ -374,10 +387,10 @@ function renderPlayer(nextState) {
|
||||
const current = Number.isInteger(nextState.currentIndex)
|
||||
? nextState.tracks[nextState.currentIndex]
|
||||
: null;
|
||||
elements.title.textContent = current ? current.title : "Nog niets gekozen";
|
||||
elements.title.textContent = current ? current.title : t("nothingSelected");
|
||||
elements.meta.textContent = current
|
||||
? [current.artist, current.album].filter(Boolean).join(" · ") || current.source
|
||||
: "Voeg een track toe vanuit de bibliotheek";
|
||||
: t("addTrackHint");
|
||||
|
||||
const artworkId = current?.artworkId || "";
|
||||
if (elements.coverImage.dataset.artworkId !== artworkId) {
|
||||
@@ -395,8 +408,8 @@ function renderPlayer(nextState) {
|
||||
|
||||
const playing = nextState.state === "playing" || nextState.state === "starting";
|
||||
elements.play.textContent = playing ? "Ⅱ" : "▶";
|
||||
elements.play.setAttribute("aria-label", playing ? "Pauzeren" : "Afspelen");
|
||||
elements.play.disabled = nextState.tracks.length === 0;
|
||||
elements.play.setAttribute("aria-label", t(playing ? "pause" : "play"));
|
||||
elements.play.disabled = nextState.tracks.length === 0 || nextState.renderers.length === 0;
|
||||
elements.previous.disabled = nextState.tracks.length === 0;
|
||||
elements.next.disabled = nextState.tracks.length === 0;
|
||||
elements.stop.disabled = nextState.state === "stopped";
|
||||
@@ -412,14 +425,20 @@ function renderPlayer(nextState) {
|
||||
|
||||
elements.volume.value = nextState.volume;
|
||||
elements.volumeValue.value = `${Math.round(nextState.volume)}%`;
|
||||
elements.volumeToggle.setAttribute(
|
||||
"aria-label",
|
||||
t("volumePercent", { value: Math.round(nextState.volume) }),
|
||||
);
|
||||
elements.repeat.dataset.repeat = nextState.repeat;
|
||||
elements.repeat.classList.toggle("active", nextState.repeat !== "off");
|
||||
const repeatNames = { off: "Herhalen uit", all: "Alles herhalen", one: "Eén track herhalen" };
|
||||
const repeatNames = { off: t("repeatOff"), all: t("repeatAll"), one: t("repeatOne") };
|
||||
elements.repeat.title = repeatNames[nextState.repeat] || repeatNames.off;
|
||||
|
||||
elements.bits.textContent = nextState.bits ? `${nextState.bits} bit` : "— bit";
|
||||
elements.rate.textContent = nextState.rate ? `${(nextState.rate / 1000).toFixed(1)} kHz` : "— kHz";
|
||||
elements.channels.textContent = nextState.channels ? `${nextState.channels} kanalen` : "— kanalen";
|
||||
elements.channels.textContent = nextState.channels
|
||||
? `${nextState.channels} ${t(nextState.channels === 1 ? "oneChannel" : "channels")}`
|
||||
: `— ${t("channels")}`;
|
||||
elements.format.textContent = nextState.format || "—";
|
||||
elements.source.textContent = nextState.source || "—";
|
||||
}
|
||||
@@ -441,7 +460,7 @@ function render(nextState) {
|
||||
renderTabs(nextState);
|
||||
renderPlaylist(nextState);
|
||||
renderPlayer(nextState);
|
||||
setStatus(nextState.error || (nextState.discovering ? "Netwerkspelers zoeken…" : ""));
|
||||
setStatus(nextState.error || (nextState.discovering ? t("searchingPlayers") : ""));
|
||||
}
|
||||
|
||||
elements.play.addEventListener("click", () => {
|
||||
@@ -458,6 +477,22 @@ elements.repeat.addEventListener("click", () => {
|
||||
command("repeat", { mode: next });
|
||||
});
|
||||
elements.renderer.addEventListener("change", () => command("renderer", { id: elements.renderer.value }));
|
||||
elements.language.value = window.RktTranslate.language();
|
||||
elements.language.addEventListener("change", async () => {
|
||||
window.RktTranslate.setLanguage(elements.language.value);
|
||||
try {
|
||||
await api("/api/preferences", { language: elements.language.value });
|
||||
} catch (error) {
|
||||
setStatus(error.message);
|
||||
}
|
||||
});
|
||||
window.addEventListener("rkt-language-change", () => {
|
||||
elements.language.value = window.RktTranslate.language();
|
||||
for (const element of [elements.renderer, elements.libraryEntries, elements.tabs, elements.playlist]) {
|
||||
delete element.dataset.signature;
|
||||
}
|
||||
if (state) render(state);
|
||||
});
|
||||
elements.discover.addEventListener("click", async () => {
|
||||
try {
|
||||
render(await api("/api/discover", {}));
|
||||
@@ -465,10 +500,29 @@ elements.discover.addEventListener("click", async () => {
|
||||
setStatus(error.message);
|
||||
}
|
||||
});
|
||||
elements.volumeToggle.addEventListener("click", () => {
|
||||
const open = !elements.volumeControl.classList.contains("open");
|
||||
elements.volumeControl.classList.toggle("open", open);
|
||||
elements.volumeToggle.setAttribute("aria-expanded", String(open));
|
||||
if (open) elements.volume.focus();
|
||||
});
|
||||
elements.volume.addEventListener("input", () => {
|
||||
elements.volumeValue.value = `${elements.volume.value}%`;
|
||||
});
|
||||
elements.volume.addEventListener("change", () => command("volume", { value: Number(elements.volume.value) }));
|
||||
document.addEventListener("pointerdown", (event) => {
|
||||
if (!elements.volumeControl.contains(event.target)) {
|
||||
elements.volumeControl.classList.remove("open");
|
||||
elements.volumeToggle.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && elements.volumeControl.classList.contains("open")) {
|
||||
elements.volumeControl.classList.remove("open");
|
||||
elements.volumeToggle.setAttribute("aria-expanded", "false");
|
||||
elements.volumeToggle.focus();
|
||||
}
|
||||
});
|
||||
elements.seek.addEventListener("pointerdown", () => { seekBusy = true; });
|
||||
elements.seek.addEventListener("change", () => {
|
||||
seekBusy = false;
|
||||
@@ -488,7 +542,7 @@ async function refresh() {
|
||||
if (error.code === "authentication-required") {
|
||||
showLogin();
|
||||
} else {
|
||||
setStatus(`Geen verbinding: ${error.message}`);
|
||||
setStatus(t("noConnection", { message: error.message }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -518,7 +572,7 @@ elements.logout.addEventListener("click", async () => {
|
||||
await api("/api/auth/logout", {});
|
||||
} finally {
|
||||
elements.logout.hidden = true;
|
||||
showLogin("Je bent uitgelogd.");
|
||||
showLogin(t("loggedOut"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user