Fix DXcam engine and improve UI: loading spinners, template card gap

DXcam engine overhaul:
- Remove all user-facing config (device_idx, output_idx, output_color)
  since these are auto-resolved or hardcoded to RGB
- Use one-shot grab() mode with retry for reliability
- Lazily create camera per display via _ensure_camera()
- Clear dxcam global factory cache to prevent stale DXGI state

UI improvements:
- Replace "Loading..." text with CSS spinner animations
- Fix template card header gap on default cards (scope padding-right
  to cards with remove button only via :has selector)
- Add auto-restart server rule to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 17:26:38 +03:00
parent 74d87fd0ab
commit 3db7ba4b0e
4 changed files with 99 additions and 66 deletions

View File

@@ -49,14 +49,14 @@
<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 class="loading-spinner"></div>
</div>
</div>
<div class="tab-panel" id="tab-displays">
<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 class="loading-spinner"></div>
</div>
</div>
<div id="displays-list" style="display: none;"></div>
@@ -69,7 +69,7 @@
</span>
</p>
<div id="templates-list" class="templates-grid">
<div class="loading" data-i18n="templates.loading">Loading templates...</div>
<div class="loading-spinner"></div>
</div>
</div>
</div>

View File

@@ -165,7 +165,8 @@ section {
gap: 20px;
}
.devices-grid > .loading {
.devices-grid > .loading,
.devices-grid > .loading-spinner {
grid-column: 1 / -1;
}
@@ -735,6 +736,27 @@ input:-webkit-autofill:focus {
color: #999;
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
}
.loading-spinner::after {
content: '';
width: 28px;
height: 28px;
border: 3px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Full-page overlay spinner */
.overlay-spinner {
position: fixed;
@@ -1815,6 +1837,9 @@ input:-webkit-autofill:focus {
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.template-card:has(.card-remove-btn) .template-card-header {
padding-right: 24px;
}