Files
CDJ900ModsWeb/builder/ui.js
T
Thiemo TizianiandClaude Opus 5.5 b388dc2739 site 1.9.0: Wide waveform mod; quick search 0.4 and the WAVEFORM switch fixed (to confirm)
Builder: wide_waveform (the big waveform to the right edge, a ZOOM /
GRID box instead of the wheel), quicksearch 0.4.1, advanced_waveforms
1.2.0; docs and the Mods page with it; Known issues: the two fixes
wait for a test on a player. Boot text Pionaarr Mods 1.9.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 04:04:10 +02:00

164 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The builder page: file, mods with their dependencies, the two
// confirmations, build and download (the work is in builder.js).
(function () {
'use strict';
var MODS = [
{ id: 'extended_bpm', name: 'Extended BPM', by: 'arnoldasr', state: 'stable',
what: 'BPM with two decimals: the tenths as a small digit above, the hundredths below.' },
{ id: 'phasemeter', name: 'Phase meter', by: 'arnoldasr', state: 'experimental',
what: 'Two-line phase meter instead of the four boxes; UTILITY › PHASE METER (was SLIP FLASHING): OFF, ON or REVERSE. The bar countdown moved clear of the lines.' },
{ id: 'advanced_waveforms', name: 'Advanced waveforms', by: 'ours', state: 'experimental',
what: 'The big waveform in rekordbox’s RGB colours, the overview as a red-to-white gradient; UTILITY › WAVEFORM: BLUE, RGB or 3BAND (lows, mids, highs).' },
{ id: 'beatjump', name: 'Beat jump', by: 'ours', state: 'experimental', needs: 'hotcuekeys',
what: 'TRACK SEARCH ◀◀ ▶▶ jumps 1/2/4/8 beats along the beat grid, at once and in phase. UTILITY › BEAT JUMP (was SCREEN SAVER).' },
{ id: 'cuecolours', name: 'Cue colours', by: 'ours', state: 'stable',
what: 'Memory cues in their rekordbox colours; rekordbox hot cues shown as lettered boxes.' },
{ id: 'hotcuekeys', name: 'Hot cue keys', by: 'ours', state: 'experimental', needs: 'cuecolours',
what: 'Hot cues A–C on the BEAT DIVIDE keys: play, gate, set and delete, saved on the USB, loaded in the background.' },
{ id: 'ledsaver', name: 'LED animation', by: 'ours', state: 'stable',
what: 'While the screen saver dims the display, the button LEDs and the jog rings play an animation. UTILITY › LED ANIMATION.' },
{ id: 'keynotation', name: 'Key notation', by: 'ours', state: 'experimental',
what: 'Every key in one notation and in its Camelot wheel colour, whatever rekordbox exported: Camelot, Open Key or classic, also in BROWSE › KEY. UTILITY › KEY NOTATION.' },
{ id: 'cuenames', name: 'Cue names', by: 'ours', state: 'experimental', needs: 'cuecolours',
what: 'The rekordbox comment of the next memory cue or hot cue (“Drop”) in the title bar, in the cue’s colour.' },
{ id: 'played', name: 'Played tracks', by: 'ours', state: 'experimental',
what: 'Tracks loaded tonight green in BROWSE; with modded players on Pro DJ Link also theirs, and [n] for a deck that has the track loaded now.' },
{ id: 'endwarn', name: 'End warning', by: 'ours', state: 'experimental',
what: 'The bar under the overview blinks red instead of white in the last 30 seconds.' },
{ id: 'quicksearch', name: 'Quick search', by: 'ours', state: 'experimental',
what: 'BROWSE held: SEARCH; BROWSE twice: the list the playing track came from, the track picked.' },
{ id: 'usbautoload', name: 'USB auto load', by: 'ours', state: 'experimental',
what: 'MY SETTINGS on a stick loaded the moment the player finds them. UTILITY › USB AUTO LOAD.' },
{ id: 'wide_waveform', name: 'Wide waveform', by: 'ours', state: 'experimental',
what: 'The big waveform runs on to the right edge where the ZOOM/GRID wheel was; a small grey box over the BPM display says ZOOM or GRID.' },
{ id: 'credit', name: 'Boot text', by: 'ours', state: 'stable',
what: 'The boot screen says “Pionaarr Mods 1.9.0” instead of “Ver 1.31”.' }
];
var chosen = {};
MODS.forEach(function (m) { chosen[m.id] = true; }); // every mod ticked at the start
var stock = false, before = null; // the stock switch, and the picks it put away
function setStock(on) {
stock = on;
if (on) {
before = {};
MODS.forEach(function (m) { before[m.id] = chosen[m.id]; chosen[m.id] = false; });
} else if (before) {
MODS.forEach(function (m) { chosen[m.id] = before[m.id]; });
}
byId('stock').setAttribute('aria-pressed', on ? 'true' : 'false');
byId('picks').classList.toggle('collapsed', on); // stock: the mods folded away
byId('pick-note').textContent = on ? 'Stock: the update will have no mods.' : '';
render();
}
var file = null;
function byId(id) { return document.getElementById(id); }
function needsOf(id) { var m = MODS.filter(function (x) { return x.id === id; })[0]; return m && m.needs; }
// A mod ticked ticks what it needs (and what that needs); one unticked
// unticks every mod that needs it, directly or not.
function toggle(id, on) {
chosen[id] = on;
var notes = [];
if (on) {
for (var n = id; needsOf(n); n = needsOf(n)) {
if (!chosen[needsOf(n)]) { chosen[needsOf(n)] = true; notes.push('Ticked ' + needsOf(n) + ' too: ' + n + ' needs it.'); }
}
} else {
var changed = true;
while (changed) {
changed = false;
MODS.forEach(function (m) {
if (m.needs && !chosen[m.needs] && chosen[m.id]) { chosen[m.id] = false; changed = true; notes.push('Unticked ' + m.id + ' too: it needs ' + m.needs + '.'); }
});
}
}
byId('pick-note').textContent = notes.join(' ');
render();
}
function render() {
var box = byId('picks');
box.innerHTML = '';
MODS.forEach(function (m) {
var row = document.createElement('label');
row.className = 'pick' + (chosen[m.id] ? ' on' : '') + (stock ? ' off' : '');
row.innerHTML =
'<input type="checkbox"' + (chosen[m.id] ? ' checked' : '') + '>' +
'<span class="pick-main"><span class="pick-name">' + m.name + ' <code>' + m.id + '</code>' +
(window.CDJ_VERSIONS && window.CDJ_VERSIONS.mods[m.id] ? ' <span class="ver">v' + window.CDJ_VERSIONS.mods[m.id] + '</span>' : '') + '</span>' +
'<span class="pick-what">' + m.what + '</span>' +
'<span class="pick-by">' + (m.by === 'ours' ? 'Pionaarr mod · made with Claude Code' : 'by arnoldasr') +
(m.needs ? ' · needs <code>' + m.needs + '</code>' : '') + '</span></span>' +
'<span class="state ' + m.state + '">' + m.state + '</span>';
row.querySelector('input').disabled = stock;
row.querySelector('input').addEventListener('change', function (e) { toggle(m.id, e.target.checked); });
box.appendChild(row);
});
ready();
}
function ready() {
var why = !file ? 'Choose the update file first.'
: !byId('agree-risk').checked || !byId('agree-ai').checked ? 'Confirm both points above to build.' : '';
byId('build').disabled = !!why;
byId('build-why').textContent = why;
}
function setFile(f) {
file = f;
byId('file-state').textContent = f ? f.name + ' · ' + (f.size / 1048576).toFixed(1) + ' MB (checked when you build)' : 'No file yet';
byId('drop').classList.toggle('has', !!f);
byId('result').hidden = true;
ready();
}
function log(msg, cls) {
var l = byId('log');
l.hidden = false;
var p = document.createElement('div');
if (cls) p.className = cls;
p.textContent = msg;
l.appendChild(p);
l.scrollTop = l.scrollHeight;
}
var url = null;
function run() {
var names = MODS.map(function (m) { return m.id; }).filter(function (id) { return chosen[id]; });
byId('build').disabled = true;
byId('result').hidden = true;
byId('log').innerHTML = '';
log('mods: ' + (names.length ? names.join(', ') : 'none (stock)'));
file.arrayBuffer().then(function (buf) {
return window.CDJBuilder.build(new Uint8Array(buf), names, log);
}).then(function (r) {
log('done: every check passed', 'ok');
if (url) URL.revokeObjectURL(url);
url = URL.createObjectURL(new Blob([r.upd], { type: 'application/octet-stream' }));
byId('download').href = url;
byId('sum').innerHTML = 'SHA-256 of the file <code>' + r.updSha + '</code><br>of the application <code>' + r.appSha + '</code>';
var ul = byId('checks');
ul.innerHTML = '';
r.checks.forEach(function (c) { var li = document.createElement('li'); li.textContent = c; ul.appendChild(li); });
byId('result').hidden = false;
}).catch(function (e) {
log('stopped: ' + e.message, 'bad');
}).then(ready);
}
document.addEventListener('DOMContentLoaded', function () {
render();
byId('file').addEventListener('change', function (e) { setFile(e.target.files[0] || null); });
var drop = byId('drop');
['dragenter', 'dragover'].forEach(function (t) { drop.addEventListener(t, function (e) { e.preventDefault(); drop.classList.add('over'); }); });
['dragleave', 'drop'].forEach(function (t) { drop.addEventListener(t, function (e) { e.preventDefault(); drop.classList.remove('over'); }); });
drop.addEventListener('drop', function (e) { if (e.dataTransfer.files[0]) setFile(e.dataTransfer.files[0]); });
byId('agree-risk').addEventListener('change', ready);
byId('agree-ai').addEventListener('change', ready);
byId('build').addEventListener('click', run);
byId('stock').addEventListener('click', function () { setStock(!stock); });
});
})();