diff --git a/server/src/wled_controller/static/app.js b/server/src/wled_controller/static/app.js
index fb1b424..b9e0631 100644
--- a/server/src/wled_controller/static/app.js
+++ b/server/src/wled_controller/static/app.js
@@ -125,29 +125,101 @@ async function loadDisplays() {
if (!data.displays || data.displays.length === 0) {
container.innerHTML = '
No displays available
';
+ document.getElementById('display-layout-canvas').innerHTML = 'No displays available
';
return;
}
+ // Render display cards with enhanced information
container.innerHTML = data.displays.map(display => `
-
${display.name}
+
Resolution:
${display.width} × ${display.height}
Position:
- ${display.x}, ${display.y}
+ (${display.x}, ${display.y})
+
+
+ Display Index:
+ ${display.index}
`).join('');
+
+ // Render visual layout
+ renderDisplayLayout(data.displays);
} catch (error) {
console.error('Failed to load displays:', error);
document.getElementById('displays-list').innerHTML =
'Failed to load displays
';
+ document.getElementById('display-layout-canvas').innerHTML =
+ 'Failed to load layout
';
}
}
+function renderDisplayLayout(displays) {
+ const canvas = document.getElementById('display-layout-canvas');
+
+ if (!displays || displays.length === 0) {
+ canvas.innerHTML = 'No displays to visualize
';
+ return;
+ }
+
+ // Calculate bounding box for all displays
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+
+ displays.forEach(display => {
+ minX = Math.min(minX, display.x);
+ minY = Math.min(minY, display.y);
+ maxX = Math.max(maxX, display.x + display.width);
+ maxY = Math.max(maxY, display.y + display.height);
+ });
+
+ const totalWidth = maxX - minX;
+ const totalHeight = maxY - minY;
+
+ // Scale factor to fit in canvas (600px wide max, maintain aspect ratio)
+ const maxCanvasWidth = 600;
+ const maxCanvasHeight = 350;
+ const scaleX = maxCanvasWidth / totalWidth;
+ const scaleY = maxCanvasHeight / totalHeight;
+ const scale = Math.min(scaleX, scaleY, 0.3); // Max 0.3 scale to keep monitors reasonably sized
+
+ const canvasWidth = totalWidth * scale;
+ const canvasHeight = totalHeight * scale;
+
+ // Create display elements
+ const displayElements = displays.map(display => {
+ const left = (display.x - minX) * scale;
+ const top = (display.y - minY) * scale;
+ const width = display.width * scale;
+ const height = display.height * scale;
+
+ return `
+
+
+ ${display.name}
+ ${display.width}×${display.height}
+
+ ${display.is_primary ? '
★
' : ''}
+
+ `;
+ }).join('');
+
+ canvas.innerHTML = `
+
+ ${displayElements}
+
+ `;
+}
+
// Load devices
async function loadDevices() {
try {
diff --git a/server/src/wled_controller/static/index.html b/server/src/wled_controller/static/index.html
index efa0767..8205541 100644
--- a/server/src/wled_controller/static/index.html
+++ b/server/src/wled_controller/static/index.html
@@ -4,6 +4,7 @@
WLED Screen Controller
+
@@ -30,6 +31,21 @@
Available Displays
+
+
+
+
Display Layout
+
+
+ Primary Display
+ Secondary Display
+
+
+
+
+ Display Information
diff --git a/server/src/wled_controller/static/style.css b/server/src/wled_controller/static/style.css
index f90375c..ba562e1 100644
--- a/server/src/wled_controller/static/style.css
+++ b/server/src/wled_controller/static/style.css
@@ -223,14 +223,151 @@ section {
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 15px;
- text-align: center;
+}
+
+.display-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 15px;
}
.display-index {
- font-size: 2rem;
+ font-size: 1.3rem;
font-weight: 700;
color: var(--info-color);
- margin-bottom: 10px;
+}
+
+.badge-primary {
+ background: var(--primary-color);
+ color: white;
+}
+
+.badge-secondary {
+ background: var(--border-color);
+ color: var(--text-secondary);
+}
+
+/* Display Layout Visualization */
+.display-layout-preview {
+ background: var(--card-bg);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 20px;
+ margin-bottom: 20px;
+}
+
+.display-layout-preview h3 {
+ margin: 0 0 15px 0;
+ font-size: 1.1rem;
+ color: var(--text-color);
+}
+
+.display-layout-canvas {
+ background: var(--bg-color);
+ border: 2px dashed var(--border-color);
+ border-radius: 8px;
+ padding: 30px;
+ min-height: 300px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.layout-container {
+ position: relative;
+ background: transparent;
+}
+
+.layout-display {
+ position: absolute;
+ border: 3px solid;
+ border-radius: 8px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
+ transition: transform 0.2s, box-shadow 0.2s;
+ cursor: help;
+}
+
+.layout-display:hover {
+ transform: scale(1.05);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
+ z-index: 10;
+}
+
+.layout-display.primary {
+ border-color: var(--primary-color);
+ background: linear-gradient(135deg, rgba(76, 175, 80, 0.15), rgba(76, 175, 80, 0.05));
+}
+
+.layout-display.secondary {
+ border-color: var(--border-color);
+ background: linear-gradient(135deg, rgba(128, 128, 128, 0.1), rgba(128, 128, 128, 0.05));
+}
+
+.layout-display-label {
+ text-align: center;
+ padding: 5px;
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.layout-display-label strong {
+ font-size: 0.9rem;
+ color: var(--text-color);
+ font-weight: 600;
+}
+
+.layout-display-label small {
+ font-size: 0.75rem;
+ color: var(--text-secondary);
+}
+
+.primary-indicator {
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ color: var(--primary-color);
+ font-size: 1.2rem;
+ text-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
+}
+
+.layout-legend {
+ display: flex;
+ gap: 20px;
+ justify-content: center;
+ margin-top: 15px;
+ padding-top: 15px;
+ border-top: 1px solid var(--border-color);
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 0.9rem;
+ color: var(--text-secondary);
+}
+
+.legend-dot {
+ width: 16px;
+ height: 16px;
+ border-radius: 3px;
+ border: 2px solid;
+}
+
+.legend-dot.primary {
+ border-color: var(--primary-color);
+ background: rgba(76, 175, 80, 0.2);
+}
+
+.legend-dot.secondary {
+ border-color: var(--border-color);
+ background: rgba(128, 128, 128, 0.2);
}
.add-device-section {