Add drag-and-drop for presets and colors, max_colors validation, and 2D grid layout

- Add drag-and-drop to reorder presets in tabs (2D grid layout)
- Add drag-and-drop to reorder colors within presets
- Add max_colors field to pattern definitions
- Hide color section when max_colors is 0
- Validate color count against pattern max_colors limit
- Store presets in 2D grid format (3 columns)
- Remove left panel from tab content, show only presets
- Update color palette to show swatches instead of hex codes
- Improve preset editor UI with visual color swatches
This commit is contained in:
2026-01-17 00:58:50 +13:00
parent 9f37dbbff0
commit 97ffc69b12
6 changed files with 1183 additions and 174 deletions

View File

@@ -163,88 +163,17 @@ async def tab_content_fragment(request, session, id):
return send_file('templates/index.html')
tab_name = tab.get('name', 'Tab ' + str(id))
device_ids = ', '.join(tab.get('names', []))
html = (
'<div class="left-panel">'
'<div class="left-panel-header">'
'<div class="ids-display">'
'<label>IDs: </label>'
'<span id="current-ids">' + device_ids + '</span>'
'</div>'
'<button id="toggle-left-panel" class="btn btn-small left-panel-toggle" title="Collapse/expand controls">◀</button>'
'</div>'
'<div class="left-panel-body">'
'<div class="color-palette-section">'
'<h3>Color Palette</h3>'
'<div id="color-palette" class="color-palette" data-tab-id="' + str(id) + '">'
'<!-- Colors will be loaded here -->'
'</div>'
'<div class="palette-actions">'
'<input type="color" id="tab-color-input" value="#ffffff">'
'<button class="btn btn-small" id="tab-color-add-btn">Add Color</button>'
'<button class="btn btn-small" id="tab-color-add-from-palette-btn">Add from Palette</button>'
'</div>'
'</div>'
'<div class="controls-section">'
'<div class="control-group">'
'<label for="brightness-slider">Brightness:</label>'
'<input type="range" id="brightness-slider" min="0" max="255" value="127" class="slider">'
'<span id="brightness-value" class="slider-value">127</span>'
'</div>'
'<div class="control-group">'
'<label for="delay-slider">Delay:</label>'
'<input type="range" id="delay-slider" min="0" max="1000" value="0" class="slider">'
'<span id="delay-value" class="slider-value">100 ms</span>'
'</div>'
'</div>'
'<div class="n-params-section">'
'<h3>N Parameters</h3>'
'<div class="n-params-grid">'
'<div class="n-param-group">'
'<label for="n1-input" id="n1-label">n1:</label>'
'<input type="number" id="n1-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n2-input" id="n2-label">n2:</label>'
'<input type="number" id="n2-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n3-input" id="n3-label">n3:</label>'
'<input type="number" id="n3-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n4-input" id="n4-label">n4:</label>'
'<input type="number" id="n4-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n5-input" id="n5-label">n5:</label>'
'<input type="number" id="n5-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n6-input" id="n6-label">n6:</label>'
'<input type="number" id="n6-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n7-input" id="n7-label">n7:</label>'
'<input type="number" id="n7-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'<div class="n-param-group">'
'<label for="n8-input" id="n8-label">n8:</label>'
'<input type="number" id="n8-input" min="0" max="255" value="10" class="n-input">'
'</div>'
'</div>'
'</div>'
'</div>'
'</div>'
'<div class="right-panel">'
'<div class="presets-section">'
'<div class="presets-section" data-tab-id="' + str(id) + '">'
'<h3>Presets</h3>'
'<div class="profiles-actions" style="margin-bottom: 1rem;">'
'<button class="btn btn-primary" id="preset-add-btn-tab">Add Preset</button>'
'</div>'
'<div id="presets-list-tab" class="presets-list">'
'<!-- Presets will be loaded here -->'
'</div>'
'</div>'
'</div>'
)
return html, 200, {'Content-Type': 'text/html'}