This commit is contained in:
1 parent
3565465f34
commit
0560079b16
3 files changed
+81
-30
No files matched your search
+55
-10
@@ -8,6 +8,8 @@ const BASE = document.documentElement.dataset.base || '/';
|
||||
const POLL_MS = 5000;
|
||||
// How long a dropped cursor waits for the player to report it got there.
|
||||
const SEEK_HOLD_MS = 10000;
|
||||
// Motion turned down in the OS means cards change places without sliding.
|
||||
const CALM = matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
const el = (sel, root = document) => root.querySelector(sel);
|
||||
const els = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
||||
@@ -58,6 +60,7 @@ const ui = {
|
||||
inputIcon: el('[data-role="input-icon"]'),
|
||||
sheet: el('[data-role="sheet"]'),
|
||||
options: el('[data-role="options"]'),
|
||||
app: el('.app'),
|
||||
avr: el('.card.avr'),
|
||||
joined: el('[data-role="joined"]'),
|
||||
avrMedia: el('[data-role="avr-media"]'),
|
||||
@@ -91,6 +94,10 @@ const avrTrack = {
|
||||
avrTrack.cover.addEventListener('error', () => { avrTrack.cover.hidden = true; });
|
||||
seekable(avrTrack);
|
||||
|
||||
// Same as a room's card: named, so a view transition can follow it when it
|
||||
// moves to the top and back.
|
||||
ui.avr.style.viewTransitionName = 'avr';
|
||||
|
||||
const rooms = {};
|
||||
let inputs = [];
|
||||
let currentInput = null;
|
||||
@@ -124,7 +131,6 @@ els('.room').forEach((node) => {
|
||||
const room = {
|
||||
key,
|
||||
node,
|
||||
slot: el(`.room-slot[data-slot="${key}"]`),
|
||||
level: el('[data-role="level"]', node),
|
||||
meter: el('[data-role="meter"]', node),
|
||||
actions: el('[data-role="actions"]', node),
|
||||
@@ -225,9 +231,8 @@ function paintRoom(room) {
|
||||
// Out of the group the button names where tapping it sends the room; in it,
|
||||
// where the card sits already says so, so the button says what it does.
|
||||
room.toggleLabel.textContent = room.grouped ? 'Ungroup' : 'Cinema';
|
||||
// Grouped, its slot is empty and the AVR's card is the one that moves up.
|
||||
room.slot.classList.toggle('on-top', playing && !room.grouped);
|
||||
placeRoom(room);
|
||||
placeCards();
|
||||
}
|
||||
|
||||
/* Song, artist, cover and progress, each only when there is one. The
|
||||
@@ -427,13 +432,24 @@ function clock(seconds) {
|
||||
}
|
||||
|
||||
/* A merged room moves into the host's card, because that is what merging
|
||||
means: one group, playing one thing. Leaving puts the card back in its
|
||||
own slot, which is why the slots exist. */
|
||||
means: one group, playing one thing. Leaving puts it back in the column,
|
||||
ahead of the rooms declared after it, so the cards keep the order they are
|
||||
declared in however they were grouped. */
|
||||
function placeRoom(room) {
|
||||
const target = room.grouped ? ui.joined : room.slot;
|
||||
if (room.node.parentElement !== target) target.appendChild(room.node);
|
||||
// Left empty, a slot would still take a gap of its own and double the space.
|
||||
room.slot.hidden = room.grouped;
|
||||
if (room.grouped) {
|
||||
if (room.node.parentElement !== ui.joined) ui.joined.appendChild(room.node);
|
||||
} else if (room.node.parentElement !== ui.app) {
|
||||
const order = Object.keys(rooms);
|
||||
const next = order.slice(order.indexOf(room.key) + 1)
|
||||
.map((key) => rooms[key].node)
|
||||
.find((node) => node.parentElement === ui.app);
|
||||
// Nothing left in the column below it means it goes last, before the host.
|
||||
ui.app.insertBefore(room.node, next || ui.avr);
|
||||
}
|
||||
// A view transition only follows an element it can name, and the name has
|
||||
// to be one of a kind on the page (see placeCards). Inside the host's card
|
||||
// the card travels with it, so it takes no name of its own there.
|
||||
room.node.style.viewTransitionName = room.grouped ? 'none' : `room-${room.key}`;
|
||||
}
|
||||
|
||||
function paintGrouping() {
|
||||
@@ -451,7 +467,36 @@ function paintGrouping() {
|
||||
const playing = inside.some((node) => node.classList.contains('playing'))
|
||||
|| avrNowPlaying?.play_state === 'play';
|
||||
ui.avr.classList.toggle('playing', playing);
|
||||
ui.avr.classList.toggle('on-top', playing);
|
||||
placeCards();
|
||||
}
|
||||
|
||||
/* Which card sits at the top of the column is CSS order (see .on-top), and
|
||||
order is not a property that transitions: the cards change places between
|
||||
one frame and the next. A view transition does the sliding instead -- the
|
||||
browser snapshots the cards where they are, runs the swap, and animates
|
||||
them to where they landed. One turn can paint several cards (a poll
|
||||
touches every room and then the group), so the move is collected and run
|
||||
once, and only started when a card really does change places. Without
|
||||
support, or with motion turned down, the cards just change places the way
|
||||
they always did. */
|
||||
let placing = false;
|
||||
function placeCards() {
|
||||
if (placing) return;
|
||||
placing = true;
|
||||
queueMicrotask(() => {
|
||||
placing = false;
|
||||
// Grouped, a room's card has left the column and the AVR's is the one
|
||||
// that moves up. The AVR's tint and its position answer the same
|
||||
// question, and paintGrouping() has just settled it.
|
||||
const wanted = Object.values(rooms)
|
||||
.map((room) => [room.node, room.playing() && !room.grouped]);
|
||||
wanted.push([ui.avr, ui.avr.classList.contains('playing')]);
|
||||
|
||||
const move = () => wanted.forEach(([node, top]) => node.classList.toggle('on-top', top));
|
||||
const moves = wanted.some(([node, top]) => node.classList.contains('on-top') !== top);
|
||||
if (!moves || CALM.matches || !document.startViewTransition) move();
|
||||
else document.startViewTransition(move);
|
||||
});
|
||||
}
|
||||
|
||||
/* Press and hold to keep moving, accelerating as you hold. */
|
||||
|
||||
Reference in new issue
Block a user