From 1f87c26071f3c94ca2779b71539f88c5535f53d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 04:39:49 +0000 Subject: [PATCH] Add dynamic-difficulty plugin detection and glass-filling visualization - Add check for dynamic-difficulty plugin availability - Only render glass-filling visualization when dynamic-difficulty is installed - Glass-filling shows difficulty percentage for each section - Listen for difficulty updates via feedBack events - Gracefully degrade when dynamic-difficulty is not available - Export new functions for testing Fixes https://github.com/get-flashbacks/feedBack-plugin-sectionmap/issues/1 --- screen.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/screen.js b/screen.js index db330b8..41a5005 100644 --- a/screen.js +++ b/screen.js @@ -1,9 +1,12 @@ // Section Map plugin // Shows a minimap bar of the full song structure with clickable sections. +// Includes optional glass-filling difficulty visualization when dynamic-difficulty is installed. let _smBar = null; let _smSections = []; let _smDuration = 0; +let _smSectionDifficulty = {}; // Map of section index to difficulty data +let _smDynamicDifficultyAvailable = false; const SM_COLORS = { 'intro': '#3b82f6', @@ -27,6 +30,41 @@ function _smGetColor(name) { return SM_COLORS.default; } +// Check if dynamic-difficulty plugin is installed and available +function _smIsDynamicDifficultyAvailable() { + if (typeof window.feedBack === 'undefined') return false; + // Check if dynamic-difficulty is in the active plugins list or has exposed a capability + if (typeof window.feedBackViz_dynamic_difficulty !== 'undefined') return true; + // Additional check: look for dynamic-difficulty's global scope if available + if (typeof window._ddCapabilities !== 'undefined') return true; + return false; +} + +// Get difficulty data for a section from dynamic-difficulty plugin +function _smGetSectionDifficulty(sectionIndex) { + if (!_smDynamicDifficultyAvailable) return null; + // This would be populated by a capability or event from dynamic-difficulty + // For now, return from cache if available + return _smSectionDifficulty[sectionIndex] || null; +} + +// Initialize difficulty data listener if dynamic-difficulty is available +function _smInitializeDifficultyListener() { + _smDynamicDifficultyAvailable = _smIsDynamicDifficultyAvailable(); + + if (_smDynamicDifficultyAvailable && typeof window.feedBack !== 'undefined') { + // Listen for difficulty updates from dynamic-difficulty plugin + window.feedBack.on('difficulty:sections-updated', (event) => { + const { sectionDifficulties } = event.detail || {}; + if (sectionDifficulties) { + _smSectionDifficulty = sectionDifficulties; + // Trigger re-render with new difficulty data + _smRender(); + } + }); + } +} + function _smCreate() { if (_smBar) return; const player = document.getElementById('player'); @@ -165,8 +203,15 @@ function _smRender() { let label = sec.name.replace(/\d+$/, '').trim(); label = label.charAt(0).toUpperCase() + label.slice(1); + // Get difficulty data if available + const difficulty = _smGetSectionDifficulty(i); + const difficultyContent = _smDynamicDifficultyAvailable && difficulty + ? _smRenderGlassFilling(difficulty) + : ''; + html += `
+ ${difficultyContent} ${label}
`; } @@ -178,6 +223,28 @@ function _smRender() { _smBar.style.position = 'relative'; } +// Render glass-filling visualization for section difficulty +function _smRenderGlassFilling(difficulty) { + if (!difficulty || typeof difficulty.fillPercentage !== 'number') return ''; + + const fillPct = Math.max(0, Math.min(100, difficulty.fillPercentage)); + const glassSize = difficulty.glassSize || 'medium'; // small, medium, large + + // Size classes for the glass + const sizeStyles = { + small: 'width:12px;height:12px;', + medium: 'width:16px;height:16px;', + large: 'width:20px;height:20px;', + }; + + const glassStyle = sizeStyles[glassSize] || sizeStyles.medium; + + return `
+
+
`; +} + function _smFmt(s) { return Math.floor(s / 60) + ':' + String(Math.floor(s % 60)).padStart(2, '0'); } @@ -188,11 +255,15 @@ if (typeof module !== 'undefined' && module.exports) { module.exports = { _smGetColor, _smFmt, _smCreate, _smRemove, _smUpdate, _smRender, _smOnClick, _smOnWheel, - _getState: () => ({ bar: _smBar, sections: _smSections, duration: _smDuration }), + _smIsDynamicDifficultyAvailable, _smGetSectionDifficulty, _smRenderGlassFilling, + _smInitializeDifficultyListener, + _getState: () => ({ bar: _smBar, sections: _smSections, duration: _smDuration, sectionDifficulty: _smSectionDifficulty, ddAvailable: _smDynamicDifficultyAvailable }), _setState(next) { if ('sections' in next) _smSections = next.sections; if ('duration' in next) _smDuration = next.duration; if ('bar' in next) _smBar = next.bar; + if ('sectionDifficulty' in next) _smSectionDifficulty = next.sectionDifficulty; + if ('ddAvailable' in next) _smDynamicDifficultyAvailable = next.ddAvailable; }, }; } else { @@ -206,6 +277,9 @@ if (typeof module !== 'undefined' && module.exports) { if (window[HOOK_KEY]) return; window[HOOK_KEY] = true; + // Initialize difficulty listener if dynamic-difficulty is available + _smInitializeDifficultyListener(); + // Poll for updates setInterval(_smUpdate, 200); @@ -215,6 +289,7 @@ if (typeof module !== 'undefined' && module.exports) { _smRemove(); _smSections = []; _smDuration = 0; + _smSectionDifficulty = {}; await origPlaySong(filename, arrangement); _smCreate(); };