feat(ui): numpad, audio readout, and sequence beat controls

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-17 18:32:12 +12:00
parent 964cfc6d91
commit c286e504eb
9 changed files with 779 additions and 159 deletions

View File

@@ -264,6 +264,8 @@ document.addEventListener('DOMContentLoaded', () => {
const presetBackgroundFromPaletteButton = document.getElementById('preset-background-from-palette-btn');
const presetModeInput = document.getElementById('preset-mode-input');
const presetModeGroup = document.getElementById('preset-mode-group');
const presetReverseInput = document.getElementById('preset-reverse-input');
const presetReverseGroup = document.getElementById('preset-reverse-group');
if (!presetsButton || !presetsModal || !presetsList || !presetSaveButton) {
return;
@@ -350,6 +352,22 @@ document.addEventListener('DOMContentLoaded', () => {
const patternSupportsModes = (patternName) => getPatternModeOptions(patternName) !== null;
const patternSupportsReverse = (patternName) => {
const cfg = resolvePatternConfig(patternName);
return !!(cfg && cfg.supports_reverse);
};
const setPresetReverseFieldVisible = (show) => {
if (!presetReverseGroup) {
return;
}
presetReverseGroup.hidden = !show;
presetReverseGroup.style.display = show ? '' : 'none';
if (!show && presetReverseInput) {
presetReverseInput.checked = false;
}
};
const setPresetModeFieldVisible = (show) => {
if (!presetModeGroup) {
return;
@@ -773,6 +791,12 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
if (presetReverseInput) {
const n5raw = preset.n5;
const n5 = typeof n5raw === 'number' ? n5raw : parseInt(String(n5raw != null ? n5raw : '0'), 10);
presetReverseInput.checked = Number.isFinite(n5) && n5 > 0;
}
// Set n values, checking both n keys and descriptive names
for (let i = 1; i <= 8; i++) {
const nKey = `n${i}`;
@@ -828,6 +852,10 @@ document.addEventListener('DOMContentLoaded', () => {
if (presetManualModeInput) {
presetManualModeInput.checked = false;
}
if (presetReverseInput) {
presetReverseInput.checked = false;
}
setPresetReverseFieldVisible(false);
if (presetManualBeatNInput) {
presetManualBeatNInput.value = '1';
}
@@ -872,7 +900,7 @@ document.addEventListener('DOMContentLoaded', () => {
const tabData = await tabRes.json();
const allowed =
typeof window.zoneAllowsPresets === 'function'
? window.zoneAllowsPresets(tabData)
? window.zoneAllowsPresets(tabData, currentEditTabId)
: true;
presetRemoveFromTabButton.hidden = !allowed;
} catch (e) {
@@ -951,13 +979,20 @@ document.addEventListener('DOMContentLoaded', () => {
const modeEntries = patternSupportsModes(payload.pattern)
? getPatternModeOptions(payload.pattern)
: null;
const reverseField = patternSupportsReverse(payload.pattern);
for (let i = 1; i <= 8; i++) {
const nKey = `n${i}`;
if (modeEntries && nKey === 'n6') {
continue;
}
if (reverseField && nKey === 'n5') {
continue;
}
payload[nKey] = getNumberInput(`preset-${nKey}-input`);
}
if (reverseField) {
payload.n5 = presetReverseInput && presetReverseInput.checked ? 1 : 0;
}
if (modeEntries && presetModeInput) {
payload.mode = parseInt(presetModeInput.value, 10) || 0;
}
@@ -1065,9 +1100,19 @@ document.addEventListener('DOMContentLoaded', () => {
}
const modeEntries = patternSupportsModes(patternName) ? getPatternModeOptions(patternName) : null;
const reverseField = patternSupportsReverse(patternName);
if (modeEntries) {
visibleNKeys.delete('n6');
}
if (reverseField) {
visibleNKeys.delete('n5');
}
setPresetReverseFieldVisible(reverseField);
if (reverseField && presetReverseInput) {
const n5raw = presetForMode && presetForMode.n5 !== undefined ? presetForMode.n5 : 0;
const n5 = typeof n5raw === 'number' ? n5raw : parseInt(String(n5raw), 10);
presetReverseInput.checked = Number.isFinite(n5) && n5 > 0;
}
if (presetModeInput) {
if (modeEntries) {
setPresetModeFieldVisible(true);
@@ -1355,7 +1400,7 @@ document.addEventListener('DOMContentLoaded', () => {
const zoneDoc = await zoneCheck.json();
if (
typeof window.zoneAllowsPresets === 'function' &&
!window.zoneAllowsPresets(zoneDoc)
!window.zoneAllowsPresets(zoneDoc, zoneId)
) {
alert('This zone is for sequences only. Add a sequence from the zone Edit menu instead.');
return;
@@ -1495,7 +1540,7 @@ document.addEventListener('DOMContentLoaded', () => {
const tabData = await tabResponse.json();
if (
typeof window.zoneAllowsPresets === 'function' &&
!window.zoneAllowsPresets(tabData)
!window.zoneAllowsPresets(tabData, zoneId)
) {
alert('This zone is for sequences only. Add a sequence from the zone Edit menu instead.');
return;
@@ -2214,7 +2259,7 @@ const savePresetGrid = async (zoneId, presetGrid) => {
const tabData = await tabResponse.json();
if (
typeof window.zoneAllowsPresets === 'function' &&
!window.zoneAllowsPresets(tabData)
!window.zoneAllowsPresets(tabData, zoneId)
) {
throw new Error('This zone is for sequences only.');
}
@@ -2312,9 +2357,11 @@ const renderTabPresets = async (zoneId, options = {}) => {
const tabData = await tabResponse.json();
const groupsMapStrip = groupsStripRes.ok ? await groupsStripRes.json() : {};
const ck =
typeof window.normalizeZoneContentKind === 'function'
? window.normalizeZoneContentKind(tabData)
: null;
typeof window.effectiveZoneContentKind === 'function'
? window.effectiveZoneContentKind(tabData)
: typeof window.normalizeZoneContentKind === 'function'
? window.normalizeZoneContentKind(tabData)
: 'presets';
// Get presets - support both 2D grid and flat array (for backward compatibility)
let presetGrid = tabData.presets;
@@ -2454,7 +2501,8 @@ const renderTabPresets = async (zoneId, options = {}) => {
if (
typeof window.appendZoneSequenceTiles === 'function' &&
(typeof window.zoneAllowsSequences !== 'function' || window.zoneAllowsSequences(tabData))
(typeof window.zoneAllowsSequences !== 'function' ||
window.zoneAllowsSequences(tabData, zoneId))
) {
await window.appendZoneSequenceTiles(zoneId, tabData, allPresets, paletteColors, presetsList);
}
@@ -2698,7 +2746,7 @@ const removePresetFromTab = async (zoneId, presetId) => {
const tabData = await tabResponse.json();
if (
typeof window.zoneAllowsPresets === 'function' &&
!window.zoneAllowsPresets(tabData)
!window.zoneAllowsPresets(tabData, zoneId)
) {
alert('This zone is for sequences only.');
return;