Files
CDJ900ModsWeb/builder/ui.js
T
Thiemo TizianiandClaude Opus 5.5 e4728b16ea Builder, upcoming page, mods page for the current mods
- builder.html: build C900NXS.UPD in the browser (official 1.31 update in,
  mods ticked with their dependencies, no-liability and Claude Code
  confirmations, download). Ports of upd.py, the LZSS packer and the mods'
  application; the result is checked against the SHA-256 the Python
  patcher gives and is byte for byte the patcher's file.
- builder/data.js from tools/make_builder_data.py: the mods' bytes per
  combination; firmware parts (UTILITY table, HOT KEYS box, the asset
  blob's resource table) are made from the user's own update.
- index.html: rgb_waveform, extended_bpm, phasemeter, cuecolours,
  hotcuekeys (experimental), ledsaver, credit; stable/experimental and a
  green "ran on a player" dot.
- upcoming.html: beat jump, stems, uniform Camelot / Open Key, 3-band
  waveform.
- docs.html: the builder, the new mod names, hot cue loading and locks.

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

119 lines
5.7 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: 'stable',
what: 'Two-line phase meter instead of the four boxes; UTILITY › PHASE METER (was SLIP FLASHING) switches it.' },
{ id: 'rgb_waveform', name: 'RGB waveform', by: 'arnoldasr', state: 'stable',
what: 'The big waveform in RGB colours, the overview as a red-to-white gradient.' },
{ 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: 'credit', name: 'Boot text', by: 'ours', state: 'stable',
what: 'The boot screen says “Pionaarr Mods 1.0” instead of “Ver 1.31”.' }
];
var chosen = {};
MODS.forEach(function (m) { chosen[m.id] = m.state === 'stable'; });
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; }
function toggle(id, on) {
chosen[id] = on;
var note = '';
if (on && needsOf(id) && !chosen[needsOf(id)]) { chosen[needsOf(id)] = true; note = 'Ticked ' + needsOf(id) + ' too: ' + id + ' needs it.'; }
if (!on) MODS.forEach(function (m) {
if (m.needs === id && chosen[m.id]) { chosen[m.id] = false; note = 'Unticked ' + m.id + ' too: it needs ' + id + '.'; }
});
byId('pick-note').textContent = note;
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' : '');
row.innerHTML =
'<input type="checkbox"' + (chosen[m.id] ? ' checked' : '') + '>' +
'<span class="pick-main"><span class="pick-name">' + m.name + ' <code>' + m.id + '</code></span>' +
'<span class="pick-what">' + m.what + '</span>' +
'<span class="pick-by">' + (m.by === 'ours' ? 'ours · 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').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);
});
})();