76 lines
3.2 KiB
JavaScript
76 lines
3.2 KiB
JavaScript
// A small cassette player on the desk: a cassette window with two reels that turn while the music plays,
|
|
// the track title on the cassette label, and five vintage keys (previous, play, pause, stop, next).
|
|
|
|
import { t } from "../i18n/i18n.js";
|
|
import { $ } from "./desk.js";
|
|
|
|
const KEYS = [
|
|
["prev", `<path d="M12 1v14L3 8zM22 1v14l-9-7z"/>`],
|
|
["play", `<path d="M7 1v14l11-7z"/>`],
|
|
["pause", `<path d="M6 1h4v14H6zM14 1h4v14h-4z"/>`],
|
|
["stop", `<path d="M6 2h12v12H6z"/>`],
|
|
["next", `<path d="M2 1v14l9-7zM12 1v14l9-7z"/>`],
|
|
];
|
|
|
|
const reel = (side) => `
|
|
<svg class="walkman__reel walkman__reel--${side}" viewBox="0 0 40 40" aria-hidden="true" focusable="false">
|
|
<circle class="walkman__tape" cx="20" cy="20" r="17"/>
|
|
<g class="walkman__spokes">${[0, 60, 120].map((a) => `<line x1="20" y1="9" x2="20" y2="31" transform="rotate(${a} 20 20)"/>`).join("")}</g>
|
|
<circle class="walkman__hub" cx="20" cy="20" r="6"/>
|
|
</svg>`;
|
|
|
|
export function createWalkman(root, { music, onPress = () => {} }) {
|
|
root.innerHTML = `
|
|
<div class="walkman__body">
|
|
<div class="walkman__badge"><b>VALDOR</b><span>${t("walkman.model")}</span><i class="walkman__led" aria-hidden="true"></i></div>
|
|
<div class="walkman__window">
|
|
${reel("left")}
|
|
<div class="walkman__label"><span class="walkman__title" role="status"></span></div>
|
|
${reel("right")}
|
|
</div>
|
|
<div class="walkman__keys" role="group" aria-label="${t("walkman.title")}">
|
|
${KEYS.map(([act, icon]) => `
|
|
<button class="walkman__key walkman__key--${act}" data-act="${act}" aria-label="${t("walkman." + act)}" title="${t("walkman." + act)}">
|
|
<svg viewBox="0 0 24 16" aria-hidden="true" focusable="false">${icon}</svg>
|
|
</button>`).join("")}
|
|
</div>
|
|
</div>`;
|
|
|
|
const title = $(root, ".walkman__title");
|
|
const label = $(root, ".walkman__label");
|
|
|
|
root.addEventListener("click", (e) => {
|
|
const b = e.target.closest("button[data-act]");
|
|
if (!b || b.disabled) return;
|
|
onPress();
|
|
music[b.dataset.act]();
|
|
});
|
|
|
|
function update() {
|
|
const m = music.info();
|
|
const playing = m.state === "playing";
|
|
root.classList.toggle("is-playing", playing);
|
|
root.classList.toggle("is-empty", !m.available);
|
|
root.title = m.available ? "" : t("walkman.hint");
|
|
|
|
let text = t("walkman.empty");
|
|
if (m.available) text = m.waitingForGesture ? t("walkman.tap") : m.state === "stopped" ? t("walkman.stopped") : m.title;
|
|
if (title.textContent !== text) {
|
|
title.textContent = text;
|
|
title.classList.remove("is-scroll");
|
|
// a long title scrolls across the label while the tape runs
|
|
requestAnimationFrame(() => title.classList.toggle("is-scroll", title.scrollWidth > label.clientWidth + 2));
|
|
}
|
|
|
|
for (const b of root.querySelectorAll("button[data-act]")) {
|
|
b.disabled = !m.available;
|
|
if (b.dataset.act === "play") b.setAttribute("aria-pressed", String(playing));
|
|
if (b.dataset.act === "pause") b.setAttribute("aria-pressed", String(m.state === "paused"));
|
|
if (b.dataset.act === "stop") b.setAttribute("aria-pressed", String(m.state === "stopped"));
|
|
}
|
|
}
|
|
|
|
music.setListener(update);
|
|
return { update };
|
|
}
|