Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
toggleSectionPracticePopover,
} from './js/section-practice.js';
import { configureHost } from './js/host.js';
import { formatTime } from './js/format.js';
// The playback transport. These used to BE app.js — they are imported back now, and the
// four modules that reached for them through the host seam import them directly instead.
import {
Expand Down Expand Up @@ -1497,7 +1498,7 @@
return _defaultLibFilters();
}
const lyrics = parsed.lyrics;
return {

Check warning on line 1501 in static/app.js

View workflow job for this annotation

GitHub Actions / ci / lint

File has too many lines (6313). Maximum allowed is 1500
arrHas: _normalizeStringArray(parsed.arrHas),
arrLacks: _normalizeStringArray(parsed.arrLacks),
stemsHas: _normalizeStringArray(parsed.stemsHas),
Expand Down Expand Up @@ -4821,7 +4822,6 @@
}


function formatTime(s) { return `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`; }



Expand Down Expand Up @@ -6261,7 +6261,6 @@
// tests/js/host_contract.test.js fails CI if this list and the host.* uses under
// static/js/ ever drift apart.
configureHost({
formatTime,
handleSliderInput,
playSong,
// count-in is a module now, so section-practice reaches it through the seam too —
Expand Down
17 changes: 17 additions & 0 deletions static/js/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Display formatters. A LEAF module: imports nothing.
//
// WHY THIS EXISTS FOR ONE FUNCTION. formatTime was a HOST HOOK — loops.js and
// section-practice.js both reached back through the seam for it. It was also, by pure
// accident of who calls it, inside the dependency closure of the library carve. Leaving
// it there would have made loops.js and section-practice.js import the LIBRARY to format
// a timestamp, which is nonsense, and a cycle waiting to happen.
//
// A hook is a cycle you agreed to live with. This one has a real owner — it just isn't
// app.js, and it certainly isn't the library. Give it a home of its own and both
// consumers import it directly.
//
// It is a leaf on purpose. Anything else that turns out to be a shared pure formatter
// belongs here too; nothing does yet, so nothing else is here.

/** Seconds -> `M:SS`. */
export function formatTime(s) { return `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, '0')}`; }
7 changes: 4 additions & 3 deletions static/js/loops.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// fails CI if the hooks used here and the hooks app.js wires ever drift apart.
import { esc, uiPrompt } from './dom.js';
import { _audioSeek, _audioTime } from './transport.js';
import { formatTime } from './format.js';
import { host } from './host.js';
import {
_setSectionPracticeMode,
Expand Down Expand Up @@ -167,11 +168,11 @@ export function updateLoopUI() {
const label = document.getElementById('loop-label');
const hasLoop = loopA !== null && loopB !== null;
if (hasLoop) {
label.textContent = `${host.formatTime(loopA)} → ${host.formatTime(loopB)}`;
label.textContent = `${formatTime(loopA)} → ${formatTime(loopB)}`;
document.getElementById('btn-loop-clear').classList.remove('hidden');
document.getElementById('btn-loop-save').classList.remove('hidden');
} else if (loopA !== null) {
label.textContent = `${host.formatTime(loopA)} → ?`;
label.textContent = `${formatTime(loopA)} → ?`;
document.getElementById('btn-loop-clear').classList.add('hidden');
document.getElementById('btn-loop-save').classList.add('hidden');
} else {
Expand All @@ -190,7 +191,7 @@ export async function loadSavedLoops() {

sel.innerHTML = '<option value="">Saved Loops</option>';
for (const l of loops) {
sel.innerHTML += `<option value="${l.id}" data-start="${l.start}" data-end="${l.end}">${esc(l.name)} (${host.formatTime(l.start)}→${host.formatTime(l.end)})</option>`;
sel.innerHTML += `<option value="${l.id}" data-start="${l.start}" data-end="${l.end}">${esc(l.name)} (${formatTime(l.start)}→${formatTime(l.end)})</option>`;
}
if (loops.length > 0) {
sel.classList.remove('hidden');
Expand Down
3 changes: 2 additions & 1 deletion static/js/section-practice.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import { audio } from './audio-el.js';
import { esc } from './dom.js';
import { _audioDuration, _audioTime, audioSeekGen } from './transport.js';
import { formatTime } from './format.js';
import { host } from './host.js';

export function _sectionPracticeBarContains(el) {
Expand Down Expand Up @@ -902,7 +903,7 @@ export function renderSectionPracticeBar() {
_showSectionPracticeBar(bar);
scroll.innerHTML = parents.map((p, i) => {
const label = _formatSectionPracticeName(p.name);
const tip = `${label} (${host.formatTime(p.start)}–${host.formatTime(p.end)})`;
const tip = `${label} (${formatTime(p.start)}–${formatTime(p.end)})`;
const kindClass = _sectionPracticeChipKindClass(p.name, i);
return `<button type="button" class="section-practice-chip${kindClass}" data-parent-idx="${i}" title="${esc(tip)}" onclick="onSectionParentClick(${i})">${esc(label)}</button>`;
}).join('');
Expand Down
Loading