Let the queue page pin the frame animation

Waiting for the shuffle bag to land on the animation you are working on makes
testing one of them tedious. The queue page now carries an Animation row next
to Background: Auto keeps the per-track cycle, any other button pins the player
to that animation until the mode changes again.

The animation names live in config.js alongside their labels, so the server can
validate them and the queue page can label its buttons; motion.js keys its
registry by the same names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tg15tLAuT6NrCE1ZxKVgDB
This commit is contained in:
simon
2026-08-09 22:12:52 +02:00
co-authored by Claude Opus 5
parent ce1a6d6a47
commit e031ebda1a
9 changed files with 159 additions and 47 deletions
+11 -1
View File
@@ -16,6 +16,8 @@ import {
CORNER_INTERVAL_SECONDS,
CORNER_MARGIN,
BACKGROUNDS,
ANIMATIONS,
AUTO_ANIMATION,
} from '../config.js';
import { parseVideoId, fetchTitle } from './youtube.js';
import * as store from './state.js';
@@ -43,7 +45,9 @@ app.get('/config.js', (_req, res) => {
`export const BOUNCE_SPEED = ${BOUNCE_SPEED};\n` +
`export const CORNER_INTERVAL_SECONDS = ${CORNER_INTERVAL_SECONDS};\n` +
`export const CORNER_MARGIN = ${CORNER_MARGIN};\n` +
`export const BACKGROUNDS = ${JSON.stringify(BACKGROUNDS)};\n`,
`export const BACKGROUNDS = ${JSON.stringify(BACKGROUNDS)};\n` +
`export const ANIMATIONS = ${JSON.stringify(ANIMATIONS)};\n` +
`export const AUTO_ANIMATION = ${JSON.stringify(AUTO_ANIMATION)};\n`,
);
});
@@ -112,6 +116,12 @@ const handlers = {
commit();
},
setAnimation(_ws, msg) {
if (!store.isAnimation(msg.key)) return;
store.getState().animation = msg.key;
commit();
},
// Relayed straight through: this arrives once a second and must never
// touch the disk.
progress(_ws, msg) {
+20 -2
View File
@@ -6,7 +6,13 @@ import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { randomUUID } from 'node:crypto';
import { DEFAULT_BACKGROUND, BACKGROUNDS } from '../config.js';
import {
DEFAULT_BACKGROUND,
BACKGROUNDS,
DEFAULT_ANIMATION,
ANIMATIONS,
AUTO_ANIMATION,
} from '../config.js';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const STORE_PATH = join(ROOT, 'queue.json');
@@ -15,12 +21,18 @@ const state = {
items: [],
currentId: null,
background: DEFAULT_BACKGROUND,
animation: DEFAULT_ANIMATION,
};
export function getState() {
return state;
}
/** Is this a name the player can be pinned to (or the auto cycle)? */
export function isAnimation(key) {
return key === AUTO_ANIMATION || key in ANIMATIONS;
}
export function load() {
try {
const saved = JSON.parse(readFileSync(STORE_PATH, 'utf8'));
@@ -35,6 +47,7 @@ export function load() {
}));
}
if (saved.background in BACKGROUNDS) state.background = saved.background;
if (isAnimation(saved.animation)) state.animation = saved.animation;
// Resume at the item that was playing, otherwise at the top of the list.
state.currentId = state.items.some((i) => i.id === saved.currentId)
? saved.currentId
@@ -52,7 +65,12 @@ export function load() {
*/
export function save() {
const payload = JSON.stringify(
{ items: state.items, currentId: state.currentId, background: state.background },
{
items: state.items,
currentId: state.currentId,
background: state.background,
animation: state.animation,
},
null,
2,
);