ht32-panel-daemon 0.9.0

Daemon with web UI for HT32 panel control
{% extends "base.html" %}

{% block content %}
<div class="card">
    <div id="status" hx-get="/status" hx-trigger="load, every 5s" hx-swap="innerHTML">
        Loading...
    </div>
</div>

<div class="card preview" id="preview-card">
    <h2>Display Preview</h2>
    <div id="preview-loading" class="loading-overlay" style="display: none;">
        <span class="spinner" style="width: 2em; height: 2em;"></span>
    </div>
    <img id="lcd-preview" src="/lcd.png" alt="LCD Preview">
</div>
<script>
(function() {
    const img = document.getElementById('lcd-preview');
    const loading = document.getElementById('preview-loading');
    let intervalId = null;
    let pendingRefresh = false;

    function showLoading() {
        loading.style.display = 'flex';
    }

    function hideLoading() {
        loading.style.display = 'none';
    }

    function refresh() {
        const next = new Image();
        next.onload = () => {
            img.src = next.src;
            hideLoading();
            pendingRefresh = false;
        };
        next.onerror = () => {
            hideLoading();
            pendingRefresh = false;
        };
        next.src = '/lcd.png?t=' + Date.now();
    }

    function refreshAfterChange() {
        if (pendingRefresh) return;
        pendingRefresh = true;
        showLoading();
        // Small delay to let the server process the change
        setTimeout(refresh, 100);
    }

    // Listen for HTMX requests to show loading on preview
    document.body.addEventListener('htmx:beforeRequest', function(evt) {
        const target = evt.detail.elt;
        // Check if this is a display-related form
        if (target.closest('#orientation, #theme-controls, #face-controls, #complications-controls, #led-controls')) {
            showLoading();
        }
    });

    document.body.addEventListener('htmx:afterSettle', function(evt) {
        const target = evt.detail.elt;
        // Refresh preview after display-related changes
        if (target.closest('#orientation, #theme-controls, #face-controls, #complications-controls')) {
            refreshAfterChange();
        }
    });

    // Server-Sent Events for D-Bus changes
    const evtSource = new EventSource('/events');

    evtSource.addEventListener('orientation', function(e) {
        htmx.trigger('#orientation', 'load');
        refreshAfterChange();
    });

    evtSource.addEventListener('display', function(e) {
        htmx.trigger('#theme-controls', 'load');
        htmx.trigger('#face-controls', 'load');
        htmx.trigger('#complications-controls', 'reload');
        refreshAfterChange();
    });

    evtSource.addEventListener('complication', function(e) {
        htmx.trigger('#complications-controls', 'reload');
        refreshAfterChange();
    });

    evtSource.addEventListener('led', function(e) {
        htmx.trigger('#led-controls', 'load');
    });

    evtSource.onerror = function() {
        // Reconnect after a delay if connection is lost
        setTimeout(function() {
            evtSource.close();
        }, 5000);
    };

    // Start preview refresh at 1Hz
    intervalId = setInterval(refresh, 1000);
})();
</script>

<div class="section">
    <h3 class="section-header">LCD Display</h3>

    <div class="card">
        <h2>Orientation</h2>
        <div id="orientation" hx-get="/orientation" hx-trigger="load" hx-swap="innerHTML">
            Loading...
        </div>
    </div>

    <div class="card-row">
        <div class="card">
            <h2>Theme</h2>
            <div id="theme-controls" hx-get="/theme" hx-trigger="load" hx-swap="innerHTML">
                Loading...
            </div>
        </div>

        <div class="card">
            <h2>Display Face</h2>
            <div id="face-controls" hx-get="/face" hx-trigger="load" hx-swap="innerHTML"
                 hx-on::after-settle="htmx.trigger('#complications-controls', 'reload')">
                Loading...
            </div>
        </div>
    </div>

    <div class="card" style="margin-top: 1rem;">
        <h2>Complications</h2>
        <div id="complications-controls" hx-get="/complications" hx-trigger="load, reload" hx-swap="innerHTML">
            Loading...
        </div>
    </div>
</div>

<div class="section">
    <h3 class="section-header">LED Strip</h3>

    <div class="card">
        <div id="led-controls" hx-get="/led" hx-trigger="load" hx-swap="innerHTML">
            Loading...
        </div>
    </div>
</div>
{% endblock %}