Restyle main page: collapsible sections, theme-aware ticks, UI polish
Some checks failed
Validate / validate (push) Failing after 9s
Some checks failed
Validate / validate (push) Failing after 9s
- Make Devices and Displays sections collapsible with persistent state - Move WLED config tip from footer to under Devices heading - Add theme-aware colors for calibration canvas ticks and chevrons - Rename sections to "Devices" and "Displays" with emoji prefix icons - Fix display layout scaling to fill available space - Remove unused footer-tip and modal code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -164,6 +164,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Show content now that translations are loaded
|
||||
document.body.style.visibility = 'visible';
|
||||
|
||||
// Restore collapsible section states
|
||||
initCollapsibleSections();
|
||||
|
||||
// Load API key from localStorage
|
||||
apiKey = localStorage.getItem('wled_api_key');
|
||||
|
||||
@@ -306,6 +309,27 @@ async function loadDisplays() {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(name) {
|
||||
const header = document.querySelector(`.collapsible-header[data-section="${name}"]`);
|
||||
const content = document.getElementById(`${name}-content`);
|
||||
if (!header || !content) return;
|
||||
const collapsed = !header.classList.contains('collapsed');
|
||||
header.classList.toggle('collapsed', collapsed);
|
||||
content.classList.toggle('collapsed', collapsed);
|
||||
localStorage.setItem(`section_${name}_collapsed`, collapsed ? '1' : '0');
|
||||
}
|
||||
|
||||
function initCollapsibleSections() {
|
||||
document.querySelectorAll('.collapsible-header[data-section]').forEach(header => {
|
||||
const name = header.getAttribute('data-section');
|
||||
const content = document.getElementById(`${name}-content`);
|
||||
if (localStorage.getItem(`section_${name}_collapsed`) === '1' && content) {
|
||||
header.classList.add('collapsed');
|
||||
content.classList.add('collapsed');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderDisplayLayout(displays) {
|
||||
const canvas = document.getElementById('display-layout-canvas');
|
||||
|
||||
@@ -329,11 +353,10 @@ function renderDisplayLayout(displays) {
|
||||
|
||||
// Scale factor to fit in canvas (respect available width, maintain aspect ratio)
|
||||
const availableWidth = canvas.clientWidth - 60; // account for padding
|
||||
const maxCanvasWidth = Math.min(600, availableWidth);
|
||||
const maxCanvasHeight = 450;
|
||||
const scaleX = maxCanvasWidth / totalWidth;
|
||||
const maxCanvasHeight = 350;
|
||||
const scaleX = availableWidth / totalWidth;
|
||||
const scaleY = maxCanvasHeight / totalHeight;
|
||||
const scale = Math.min(scaleX, scaleY, 0.3); // Max 0.3 scale to keep monitors reasonably sized
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
const canvasWidth = totalWidth * scale;
|
||||
const canvasHeight = totalHeight * scale;
|
||||
@@ -1199,6 +1222,12 @@ function renderCalibrationCanvas() {
|
||||
|
||||
const totalLeds = calibration.leds_top + calibration.leds_right + calibration.leds_bottom + calibration.leds_left;
|
||||
|
||||
// Theme-aware colors
|
||||
const isDark = document.documentElement.getAttribute('data-theme') !== 'light';
|
||||
const tickStroke = isDark ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.3)';
|
||||
const tickFill = isDark ? 'rgba(255, 255, 255, 0.65)' : 'rgba(0, 0, 0, 0.6)';
|
||||
const chevronStroke = isDark ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.4)';
|
||||
|
||||
// Edge bar geometry (matches CSS: corner zones 56px × 36px fixed)
|
||||
const cw = 56;
|
||||
const ch = 36;
|
||||
@@ -1287,9 +1316,9 @@ function renderCalibrationCanvas() {
|
||||
|
||||
// Tick styling
|
||||
const tickLen = 5;
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
|
||||
ctx.strokeStyle = tickStroke;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.65)';
|
||||
ctx.fillStyle = tickFill;
|
||||
ctx.font = '12px -apple-system, BlinkMacSystemFont, sans-serif';
|
||||
|
||||
labelsToShow.forEach(i => {
|
||||
@@ -1347,7 +1376,7 @@ function renderCalibrationCanvas() {
|
||||
ctx.translate(mx, my);
|
||||
ctx.rotate(angle);
|
||||
ctx.fillStyle = 'rgba(76, 175, 80, 0.85)';
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
|
||||
ctx.strokeStyle = chevronStroke;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
@@ -32,31 +32,13 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="displays-section">
|
||||
<h2 data-i18n="displays.layout">Display Layout</h2>
|
||||
|
||||
<div class="display-layout-preview">
|
||||
<div id="display-layout-canvas" class="display-layout-canvas">
|
||||
<div class="loading" data-i18n="displays.loading">Loading layout...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="displays-list" style="display: none;"></div>
|
||||
</section>
|
||||
|
||||
<section class="devices-section">
|
||||
<div class="section-header">
|
||||
<h2 data-i18n="devices.title">WLED Devices</h2>
|
||||
<h2 class="collapsible-header" onclick="toggleSection('devices')" data-section="devices"><span data-i18n="devices.title">💡 Devices</span><span class="collapse-chevron">▼</span></h2>
|
||||
<button class="btn btn-icon btn-primary" onclick="showAddDevice()" data-i18n-title="devices.add" title="Add New Device">+</button>
|
||||
</div>
|
||||
<div id="devices-list" class="devices-grid">
|
||||
<div class="loading" data-i18n="devices.loading">Loading devices...</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="footer-content">
|
||||
<p class="footer-tip">
|
||||
<div id="devices-content" class="collapsible-content">
|
||||
<p class="section-tip">
|
||||
<strong><span data-i18n="devices.wled_config">WLED Configuration:</span></strong> <span data-i18n="devices.wled_note">Configure your WLED device (effects, segments, color order, power limits, etc.) using the</span>
|
||||
<a href="https://kno.wled.ge/" target="_blank" rel="noopener" data-i18n="devices.wled_link">official WLED app</a>
|
||||
<span data-i18n="devices.wled_note_or">or the built-in</span>
|
||||
@@ -64,6 +46,26 @@
|
||||
<span data-i18n="devices.wled_note_webui">(open your device's IP in a browser).</span>
|
||||
<span data-i18n="devices.wled_note2">This controller sends pixel color data and controls brightness per device.</span>
|
||||
</p>
|
||||
<div id="devices-list" class="devices-grid">
|
||||
<div class="loading" data-i18n="devices.loading">Loading devices...</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="displays-section">
|
||||
<h2 class="collapsible-header" onclick="toggleSection('displays')" data-section="displays"><span data-i18n="displays.layout">🖥️ Displays</span><span class="collapse-chevron">▼</span></h2>
|
||||
<div id="displays-content" class="collapsible-content">
|
||||
<div class="display-layout-preview">
|
||||
<div id="display-layout-canvas" class="display-layout-canvas">
|
||||
<div class="loading" data-i18n="displays.loading">Loading layout...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="displays-list" style="display: none;"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="footer-content">
|
||||
<p>
|
||||
Created by <strong>Alexei Dolgolyov</strong>
|
||||
• <a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"auth.logout.success": "Logged out successfully",
|
||||
"auth.please_login": "Please login to view",
|
||||
"displays.title": "Available Displays",
|
||||
"displays.layout": "Display Layout",
|
||||
"displays.layout": "\uD83D\uDDA5\uFE0F Displays",
|
||||
"displays.information": "Display Information",
|
||||
"displays.legend.primary": "Primary Display",
|
||||
"displays.legend.secondary": "Secondary Display",
|
||||
@@ -32,7 +32,7 @@
|
||||
"displays.loading": "Loading displays...",
|
||||
"displays.none": "No displays available",
|
||||
"displays.failed": "Failed to load displays",
|
||||
"devices.title": "WLED Devices",
|
||||
"devices.title": "\uD83D\uDCA1 Devices",
|
||||
"devices.add": "Add New Device",
|
||||
"devices.loading": "Loading devices...",
|
||||
"devices.none": "No devices configured",
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"auth.logout.success": "Выход выполнен успешно",
|
||||
"auth.please_login": "Пожалуйста, войдите для просмотра",
|
||||
"displays.title": "Доступные Дисплеи",
|
||||
"displays.layout": "Расположение Дисплеев",
|
||||
"displays.layout": "\uD83D\uDDA5\uFE0F Дисплеи",
|
||||
"displays.information": "Информация о Дисплеях",
|
||||
"displays.legend.primary": "Основной Дисплей",
|
||||
"displays.legend.secondary": "Вторичный Дисплей",
|
||||
@@ -32,7 +32,7 @@
|
||||
"displays.loading": "Загрузка дисплеев...",
|
||||
"displays.none": "Нет доступных дисплеев",
|
||||
"displays.failed": "Не удалось загрузить дисплеи",
|
||||
"devices.title": "WLED Устройства",
|
||||
"devices.title": "\uD83D\uDCA1 Устройства",
|
||||
"devices.add": "Добавить Новое Устройство",
|
||||
"devices.loading": "Загрузка устройств...",
|
||||
"devices.none": "Устройства не настроены",
|
||||
|
||||
@@ -374,6 +374,42 @@ section {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* Collapsible sections */
|
||||
.collapsible-header {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.collapsible-header .collapse-chevron {
|
||||
display: inline-block;
|
||||
font-size: 0.6em;
|
||||
margin-left: 6px;
|
||||
transition: transform 0.2s ease;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.collapsible-header.collapsed .collapse-chevron {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease, opacity 0.2s ease;
|
||||
max-height: 2000px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.collapsible-content.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Display Layout Visualization */
|
||||
.display-layout-preview {
|
||||
background: var(--card-bg);
|
||||
@@ -527,13 +563,29 @@ section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-tip {
|
||||
font-size: 0.82rem;
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 15px 0;
|
||||
line-height: 1.5;
|
||||
padding: 8px 12px;
|
||||
background: rgba(33, 150, 243, 0.08);
|
||||
border-left: 3px solid var(--info-color, #2196F3);
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.section-tip a {
|
||||
color: var(--info-color, #2196F3);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@@ -794,19 +846,6 @@ input:-webkit-autofill:focus {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer-tip {
|
||||
margin-bottom: 12px !important;
|
||||
padding: 10px 16px;
|
||||
background: rgba(33, 150, 243, 0.08);
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.footer-tip a {
|
||||
color: var(--info-color) !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.footer-content strong {
|
||||
color: var(--text-color);
|
||||
|
||||
Reference in New Issue
Block a user