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
|
// Show content now that translations are loaded
|
||||||
document.body.style.visibility = 'visible';
|
document.body.style.visibility = 'visible';
|
||||||
|
|
||||||
|
// Restore collapsible section states
|
||||||
|
initCollapsibleSections();
|
||||||
|
|
||||||
// Load API key from localStorage
|
// Load API key from localStorage
|
||||||
apiKey = localStorage.getItem('wled_api_key');
|
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) {
|
function renderDisplayLayout(displays) {
|
||||||
const canvas = document.getElementById('display-layout-canvas');
|
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)
|
// Scale factor to fit in canvas (respect available width, maintain aspect ratio)
|
||||||
const availableWidth = canvas.clientWidth - 60; // account for padding
|
const availableWidth = canvas.clientWidth - 60; // account for padding
|
||||||
const maxCanvasWidth = Math.min(600, availableWidth);
|
const maxCanvasHeight = 350;
|
||||||
const maxCanvasHeight = 450;
|
const scaleX = availableWidth / totalWidth;
|
||||||
const scaleX = maxCanvasWidth / totalWidth;
|
|
||||||
const scaleY = maxCanvasHeight / totalHeight;
|
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 canvasWidth = totalWidth * scale;
|
||||||
const canvasHeight = totalHeight * 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;
|
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)
|
// Edge bar geometry (matches CSS: corner zones 56px × 36px fixed)
|
||||||
const cw = 56;
|
const cw = 56;
|
||||||
const ch = 36;
|
const ch = 36;
|
||||||
@@ -1287,9 +1316,9 @@ function renderCalibrationCanvas() {
|
|||||||
|
|
||||||
// Tick styling
|
// Tick styling
|
||||||
const tickLen = 5;
|
const tickLen = 5;
|
||||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
|
ctx.strokeStyle = tickStroke;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.65)';
|
ctx.fillStyle = tickFill;
|
||||||
ctx.font = '12px -apple-system, BlinkMacSystemFont, sans-serif';
|
ctx.font = '12px -apple-system, BlinkMacSystemFont, sans-serif';
|
||||||
|
|
||||||
labelsToShow.forEach(i => {
|
labelsToShow.forEach(i => {
|
||||||
@@ -1347,7 +1376,7 @@ function renderCalibrationCanvas() {
|
|||||||
ctx.translate(mx, my);
|
ctx.translate(mx, my);
|
||||||
ctx.rotate(angle);
|
ctx.rotate(angle);
|
||||||
ctx.fillStyle = 'rgba(76, 175, 80, 0.85)';
|
ctx.fillStyle = 'rgba(76, 175, 80, 0.85)';
|
||||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
|
ctx.strokeStyle = chevronStroke;
|
||||||
ctx.lineWidth = 1;
|
ctx.lineWidth = 1;
|
||||||
ctx.lineCap = 'round';
|
ctx.lineCap = 'round';
|
||||||
ctx.lineJoin = 'round';
|
ctx.lineJoin = 'round';
|
||||||
|
|||||||
@@ -32,31 +32,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</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">
|
<section class="devices-section">
|
||||||
<div class="section-header">
|
<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>
|
<button class="btn btn-icon btn-primary" onclick="showAddDevice()" data-i18n-title="devices.add" title="Add New Device">+</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="devices-list" class="devices-grid">
|
<div id="devices-content" class="collapsible-content">
|
||||||
<div class="loading" data-i18n="devices.loading">Loading devices...</div>
|
<p class="section-tip">
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer class="app-footer">
|
|
||||||
<div class="footer-content">
|
|
||||||
<p class="footer-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>
|
<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>
|
<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>
|
<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_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>
|
<span data-i18n="devices.wled_note2">This controller sends pixel color data and controls brightness per device.</span>
|
||||||
</p>
|
</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>
|
<p>
|
||||||
Created by <strong>Alexei Dolgolyov</strong>
|
Created by <strong>Alexei Dolgolyov</strong>
|
||||||
• <a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
|
• <a href="mailto:dolgolyov.alexei@gmail.com">dolgolyov.alexei@gmail.com</a>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"auth.logout.success": "Logged out successfully",
|
"auth.logout.success": "Logged out successfully",
|
||||||
"auth.please_login": "Please login to view",
|
"auth.please_login": "Please login to view",
|
||||||
"displays.title": "Available Displays",
|
"displays.title": "Available Displays",
|
||||||
"displays.layout": "Display Layout",
|
"displays.layout": "\uD83D\uDDA5\uFE0F Displays",
|
||||||
"displays.information": "Display Information",
|
"displays.information": "Display Information",
|
||||||
"displays.legend.primary": "Primary Display",
|
"displays.legend.primary": "Primary Display",
|
||||||
"displays.legend.secondary": "Secondary Display",
|
"displays.legend.secondary": "Secondary Display",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"displays.loading": "Loading displays...",
|
"displays.loading": "Loading displays...",
|
||||||
"displays.none": "No displays available",
|
"displays.none": "No displays available",
|
||||||
"displays.failed": "Failed to load displays",
|
"displays.failed": "Failed to load displays",
|
||||||
"devices.title": "WLED Devices",
|
"devices.title": "\uD83D\uDCA1 Devices",
|
||||||
"devices.add": "Add New Device",
|
"devices.add": "Add New Device",
|
||||||
"devices.loading": "Loading devices...",
|
"devices.loading": "Loading devices...",
|
||||||
"devices.none": "No devices configured",
|
"devices.none": "No devices configured",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"auth.logout.success": "Выход выполнен успешно",
|
"auth.logout.success": "Выход выполнен успешно",
|
||||||
"auth.please_login": "Пожалуйста, войдите для просмотра",
|
"auth.please_login": "Пожалуйста, войдите для просмотра",
|
||||||
"displays.title": "Доступные Дисплеи",
|
"displays.title": "Доступные Дисплеи",
|
||||||
"displays.layout": "Расположение Дисплеев",
|
"displays.layout": "\uD83D\uDDA5\uFE0F Дисплеи",
|
||||||
"displays.information": "Информация о Дисплеях",
|
"displays.information": "Информация о Дисплеях",
|
||||||
"displays.legend.primary": "Основной Дисплей",
|
"displays.legend.primary": "Основной Дисплей",
|
||||||
"displays.legend.secondary": "Вторичный Дисплей",
|
"displays.legend.secondary": "Вторичный Дисплей",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"displays.loading": "Загрузка дисплеев...",
|
"displays.loading": "Загрузка дисплеев...",
|
||||||
"displays.none": "Нет доступных дисплеев",
|
"displays.none": "Нет доступных дисплеев",
|
||||||
"displays.failed": "Не удалось загрузить дисплеи",
|
"displays.failed": "Не удалось загрузить дисплеи",
|
||||||
"devices.title": "WLED Устройства",
|
"devices.title": "\uD83D\uDCA1 Устройства",
|
||||||
"devices.add": "Добавить Новое Устройство",
|
"devices.add": "Добавить Новое Устройство",
|
||||||
"devices.loading": "Загрузка устройств...",
|
"devices.loading": "Загрузка устройств...",
|
||||||
"devices.none": "Устройства не настроены",
|
"devices.none": "Устройства не настроены",
|
||||||
|
|||||||
@@ -374,6 +374,42 @@ section {
|
|||||||
font-size: 1.2rem;
|
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 Visualization */
|
||||||
.display-layout-preview {
|
.display-layout-preview {
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
@@ -527,13 +563,29 @@ section {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-header h2 {
|
.section-header h2 {
|
||||||
margin-bottom: 0;
|
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 {
|
.form-group {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
@@ -794,19 +846,6 @@ input:-webkit-autofill:focus {
|
|||||||
margin: 0;
|
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 {
|
.footer-content strong {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
|||||||
Reference in New Issue
Block a user