Commit 6b02b2f5 authored by vertighel's avatar vertighel
Browse files

refactor: viewer_panel sub-macros, Bootstrap classes, JS fixes



- viewer_panel.html: ristruttura in sotto-macro (_canvas, _mini, _controls)
  con compact=False param; sostituisce inline style con classi BS
- fits-viewer.js: fix reset vmin/vmax (autoUpdate + dataset.camId)
- control.js, guider-panel.js: usa MODES per postExpose, normalizza scicam loop
- control.html, viewer.html: fix d.running → d.response nel loop GET

Co-Authored-By: default avatarClaude Sonnet 4.6 <noreply@anthropic.com>
parent 8551b9df
Loading
Loading
Loading
Loading

CLAUDE.md

0 → 100644
+5 −0
Original line number Diff line number Diff line
# Istruzioni per Claude

## Git

Mai eseguire `git commit` o `git push` senza istruzione esplicita nel turno corrente.
+4 −4
Original line number Diff line number Diff line
@@ -188,14 +188,14 @@
  <article class="col-xl-5 col-12">
    <div class="card bg-dark border-secondary shadow-sm">
      
      <div class="card-header p-0 border-secondary">
        <ul class="nav nav-tabs border-0" role="tablist">
      <div class="card-header p-0 border-secondary d-flex align-items-center">
        <ul class="nav nav-tabs border-0 flex-grow-1" role="tablist">
          <li class="nav-item"><button class="nav-link active py-2" data-bs-toggle="tab" data-bs-target="#mon-fits">FITS Viewer</button></li>
          <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-webcam">Webcam</button></li>
          <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-synoptic">Synoptic</button></li>
          <li class="nav-item"><button class="nav-link py-2" data-bs-toggle="tab" data-bs-target="#mon-output">Output</button></li>
        </ul>
        <small class="text-muted px-3" id="active-station-id">STATION1</small>
        <strong class="text-info px-3" id="active-station-id">STATION1</strong>
      </div>
      
      <div class="tab-content">
@@ -279,7 +279,7 @@

            fetch(`${apiBase}/${cam_id}/loop`)
                .then(r => r.json())
                .then(d => setLoopUI(d.running))
                .then(d => setLoopUI(d.response))
                .catch(() => {});
        }

+143 −189
Original line number Diff line number Diff line
@@ -18,33 +18,44 @@
     {% endcall %}
#}

{% macro camera_panel(panel_id, cam_id, has_loop, api_base, layout='full') %}
<div id="viewer-panel-{{ panel_id }}"
     class="viewer-panel"
     data-cam-id="{{ cam_id }}"
     data-api-base="{{ api_base }}">

    {% set hdr_cls = 'd-flex justify-content-between align-items-center' if layout == 'full' else 'd-flex flex-column align-items-start gap-1' %}
    <div class="{{ hdr_cls }} mb-2">
        <span class="text-uppercase text-muted"
              style="font-size:.75rem; letter-spacing:.1em;">
            {{ cam_id }}
        </span>
        <button class="btn btn-sm btn-outline-secondary btn-refresh-{{ panel_id }}"
                title="Fetch current image">↺ Refresh</button>
{# ── Atoms ──────────────────────────────────────────────────────────────────
   These three macros are internal helpers (underscore prefix = not exported).
   camera_panel assembles them; each piece is written exactly once.
#}

{% macro _canvas() %}
<div class="viewer-canvas-wrap position-relative bg-black overflow-hidden rounded">
    <canvas class="cv-main d-block w-100" width="800" height="800"
            style="cursor:crosshair; transform-origin:0 0;"></canvas>
    {#- cv-overlay: transparent 2-D canvas for annotations (crosshairs, ROI, etc.)
        pointer-events:none passes all mouse events through to cv-main.
        Not CSS-transformed; fits-viewer.js imageToCanvas() handles the coordinate mapping. -#}
    <canvas class="cv-overlay position-absolute top-0 start-0 w-100 pe-none"
            width="800" height="800"></canvas>
    <div class="cv-crosshair position-absolute pe-none d-none translate-middle"
         style="width:12px; height:12px;">
        <svg width="12" height="12" viewBox="0 0 12 12">
            <line x1="6" y1="0" x2="6" y2="12" stroke="#ffff00" stroke-width="1"/>
            <line x1="0" y1="6" x2="12" y2="6" stroke="#ffff00" stroke-width="1"/>
        </svg>
    </div>
    <div class="position-absolute bottom-0 start-0 m-2">
        <span class="badge bg-black bg-opacity-75 info-main font-monospace"
              style="font-size:.65rem;"></span>
    </div>
</div>
{% endmacro %}

    {% if layout == 'compact' %}
    {# ── Compact: explore+panner row on top, canvas full-width below ─────── #}

    <div class="row g-1 mb-2">
        <div class="col-6" style="background:#000; border-radius:4px; overflow:hidden;">
            <canvas class="cv-explore d-block" width="256" height="256"
                    style="width:100%; image-rendering:pixelated;"></canvas>
{% macro _mini(mini_col) %}
<div class="row g-1">
    <div class="{{ mini_col }} bg-black rounded-1 overflow-hidden">
        <canvas class="cv-explore d-block w-100" width="256" height="256"
                style="image-rendering:pixelated;"></canvas>
    </div>
        <div class="col-6" style="background:#000; border-radius:4px; overflow:hidden;">
            <canvas class="cv-panoramic d-block" width="256" height="256"
                    style="width:100%; height:auto;"></canvas>
    <div class="{{ mini_col }} bg-black rounded-1 overflow-hidden">
        <canvas class="cv-panoramic d-block w-100 h-auto" width="256" height="256"></canvas>
    </div>
    <div class="col-12 mt-1">
        <div class="input-group input-group-sm">
@@ -52,147 +63,80 @@
                  style="font-size:.65rem; padding:2px 6px;
                         background:#1a1a1a; border-color:#333; color:#aaa;">Box</span>
            <input type="number" class="ctrl-explore-w form-control form-control-sm"
                       value="70" step="10"
                   value="32" step="8"
                   style="background:#111; border-color:#333; font-size:.7rem; color:#b0ffb0;">
        </div>
    </div>
</div>
{% endmacro %}

    <div class="viewer-canvas-wrap position-relative"
         style="background:#000; overflow:hidden; border-radius:6px;">
        <canvas class="cv-main d-block" width="800" height="800"
                style="width:100%; cursor:crosshair; transform-origin:0 0;"></canvas>
        {#- cv-overlay: transparent 2-D canvas for annotations (crosshairs, ROI, etc.)
            pointer-events:none passes all mouse events through to cv-main.
            Not CSS-transformed; fits-viewer.js imageToCanvas() handles the coordinate mapping. -#}
        <canvas class="cv-overlay position-absolute top-0 start-0" width="800" height="800"
                style="width:100%; pointer-events:none;"></canvas>
        <div class="cv-crosshair position-absolute"
             style="pointer-events:none; width:12px; height:12px;
                    transform:translate(-50%,-50%); display:none;">
            <svg width="12" height="12" viewBox="0 0 12 12">
                <line x1="6" y1="0" x2="6" y2="12" stroke="#ffff00" stroke-width="1"/>
                <line x1="0" y1="6" x2="12" y2="6" stroke="#ffff00" stroke-width="1"/>
            </svg>
        </div>
        <div class="position-absolute bottom-0 start-0 m-2">
            <span class="badge bg-black bg-opacity-75 info-main"
                  style="font-family:monospace; font-size:.65rem;"></span>
        </div>
    </div>

{% macro _controls(panel_id, cam_id, has_loop, compact=False) %}
<div class="viewer-controls mt-2 p-2 rounded"
     style="background:#0d0d0d; border:1px solid #222;">
        <div class="row g-2 align-items-center flex-wrap">
            <div class="col-auto d-flex flex-column gap-1">
                <div class="d-flex align-items-center gap-1">
                    <label class="text-muted small" style="min-width:2rem">Min</label>
                    <input type="number" class="ctrl-vmin form-control form-control-sm"
                           style="width:90px; background:#111; border-color:#333;" step="any">
                </div>
                <div class="d-flex align-items-center gap-1">
                    <label class="text-muted small" style="min-width:2rem">Max</label>
                    <input type="number" class="ctrl-vmax form-control form-control-sm"
                           style="width:90px; background:#111; border-color:#333;" step="any">

    {% if compact %}
    {# ── Compact: stacked vertically, no label ───────────────────────────── #}
    <div class="d-flex flex-column gap-2">
        <div class="input-group input-group-sm">
            <input type="number" class="ctrl-vmin form-control bg-black" step="any">
            <input type="number" class="ctrl-vmax form-control bg-black" step="any">
        </div>
        <div class="input-group input-group-sm">
            <button class="btn btn-sm btn-outline-secondary btn-reset-{{ panel_id }}">Reset</button>
            <button class="btn btn-sm btn-outline-secondary btn-refresh-{{ panel_id }}"
                    title="Fetch current image"></button>
        </div>
            <div class="col-auto d-flex align-items-center gap-1">
        <div class="d-flex align-items-center gap-1">
            <input type="checkbox" class="ctrl-auto form-check-input"
                   id="ctrl-auto-{{ panel_id }}"
                   data-cam-id="{{ cam_id }}" checked>
                <label class="text-muted small"
                       for="ctrl-auto-{{ panel_id }}">Auto&nbsp;update</label>
            <label class="small"
                   for="ctrl-auto-{{ panel_id }}">Auto</label>
        </div>
        {% if has_loop %}
            <div class="col-auto">
        <div class="input-group input-group-sm">
            <input type="number" id="loop-exp-{{ panel_id }}"
                           class="form-control" value="1" min="0.1" step="0.5"
                           title="Exposure (s)"
                           style="background:#111; border-color:#333; max-width:70px;">
                   class="form-control bg-black" value="1" min="0.1" step="0.5"
                   title="Exposure (s)" style="max-width:70px;">
            <button id="loop-start-{{ panel_id }}"
                    class="btn btn-primary">Start</button>
            <button id="loop-stop-{{ panel_id }}"
                    class="btn btn-outline-secondary" disabled>Stop</button>
        </div>
            </div>
        {% endif %}
    </div>
    </div>

    {% else %}
    {# ── Standard two-column layout (full / stacked) ─────────────────────── #}

    <div class="row g-2 align-items-start">

        {# Left: main canvas + controls strip #}
        <div class="col-xl-9 col-lg-8">

            <div class="viewer-canvas-wrap position-relative"
                 style="background:#000; overflow:hidden; border-radius:6px;">
                <canvas class="cv-main d-block" width="800" height="800"
                        style="width:100%; cursor:crosshair; transform-origin:0 0;"></canvas>
                {#- cv-overlay: transparent annotation layer above cv-main.
                    See compact layout above for full comment. -#}
                <canvas class="cv-overlay position-absolute top-0 start-0" width="800" height="800"
                        style="width:100%; pointer-events:none;"></canvas>
                <div class="cv-crosshair position-absolute"
                     style="pointer-events:none; width:12px; height:12px;
                            transform:translate(-50%,-50%); display:none;">
                    <svg width="12" height="12" viewBox="0 0 12 12">
                        <line x1="6" y1="0" x2="6" y2="12" stroke="#ffff00" stroke-width="1"/>
                        <line x1="0" y1="6" x2="12" y2="6" stroke="#ffff00" stroke-width="1"/>
                    </svg>
                </div>
                <div class="position-absolute bottom-0 start-0 m-2">
                    <span class="badge bg-black bg-opacity-75 info-main"
                          style="font-family:monospace; font-size:.65rem;"></span>
                </div>
            </div>

            <div class="viewer-controls mt-2 p-2 rounded"
                 style="background:#0d0d0d; border:1px solid #222;">
    {# ── Full/stacked: horizontal row with Min/Max label ─────────────────── #}
    <div class="row g-2 align-items-center flex-wrap">
                    {% if layout == 'full' %}
                    <div class="col-auto d-flex align-items-center gap-1">
                        <label class="text-muted small">Min</label>
                        <input type="number" class="ctrl-vmin form-control form-control-sm"
                               style="width:90px; background:#111; border-color:#333;" step="any">
                        <label class="text-muted small">Max</label>
                        <input type="number" class="ctrl-vmax form-control form-control-sm"
                               style="width:90px; background:#111; border-color:#333;" step="any">
                        <button class="btn btn-sm btn-outline-secondary btn-reset-{{ panel_id }}">Reset</button>
        <label class="col-md-2 col-form-label">Min/Max</label>
        <div class="col-md">
            <div class="input-group input-group-sm">
                <input type="number" class="ctrl-vmin form-control bg-black" step="any">
                <input type="number" class="ctrl-vmax form-control bg-black" step="any">
            </div>
                    {% else %}
                    <div class="col-auto d-flex flex-column gap-1">
                        <div class="d-flex align-items-center gap-1">
                            <label class="text-muted small" style="min-width:2rem">Min</label>
                            <input type="number" class="ctrl-vmin form-control form-control-sm"
                                   style="width:90px; background:#111; border-color:#333;" step="any">
        </div>
                        <div class="d-flex align-items-center gap-1">
                            <label class="text-muted small" style="min-width:2rem">Max</label>
                            <input type="number" class="ctrl-vmax form-control form-control-sm"
                                   style="width:90px; background:#111; border-color:#333;" step="any">
        <div class="col-md">
            <div class="input-group input-group-sm">
                <button class="btn btn-sm btn-outline-secondary btn-reset-{{ panel_id }}">Reset</button>
                <button class="btn btn-sm btn-outline-secondary btn-refresh-{{ panel_id }}"
                        title="Fetch current image"></button>
            </div>
        </div>
                    {% endif %}
        <div class="col-auto d-flex align-items-center gap-1">
            <input type="checkbox" class="ctrl-auto form-check-input"
                   id="ctrl-auto-{{ panel_id }}"
                   data-cam-id="{{ cam_id }}" checked>
                        <label class="text-muted small"
                               for="ctrl-auto-{{ panel_id }}">Auto&nbsp;update</label>
            <label class="small"
                   for="ctrl-auto-{{ panel_id }}">Auto</label>
        </div>
        {% if has_loop %}
        <div class="col-auto">
            <div class="input-group input-group-sm">
                <input type="number" id="loop-exp-{{ panel_id }}"
                                   class="form-control" value="1" min="0.1" step="0.5"
                                   title="Exposure (s)"
                                   style="background:#111; border-color:#333; max-width:70px;">
                       class="form-control bg-black" value="1" min="0.1" step="0.5"
                       title="Exposure (s)" style="max-width:70px;">
                <button id="loop-start-{{ panel_id }}"
                        class="btn btn-primary">Start</button>
                <button id="loop-stop-{{ panel_id }}"
@@ -201,44 +145,54 @@
        </div>
        {% endif %}
    </div>

    {% endif %}

</div>
{% endmacro %}


{# ── Public macro ────────────────────────────────────────────────────────── #}

{% macro camera_panel(panel_id, cam_id, has_loop, api_base, layout='full') %}
<div id="viewer-panel-{{ panel_id }}"
     class="viewer-panel"
     data-cam-id="{{ cam_id }}"
     data-api-base="{{ api_base }}">

    <div class="mb-2">
        <span class="text-uppercase text-muted"
              style="font-size:.75rem; letter-spacing:.1em;">
            {{ cam_id }}
        </span>
    </div>

        {# Right: explore + panner + nested slot #}
        <div class="col-xl-3 col-lg-4 d-flex flex-column gap-2">
    {% if layout == 'compact' %}
    {# ── Compact: explore+panner row on top, canvas full-width below ─────── #}

    <div class="mb-2">{{ _mini('col-6') }}</div>
    {{ _canvas() }}
    {{ _controls(panel_id, cam_id, has_loop, compact=True) }}

    {% else %}
    {# ── Standard two-column layout (full / stacked) ─────────────────────── #}

            {# 'full' → side by side (col-6); 'stacked' → stacked (col-12) #}
    {% set mini_col = 'col-6' if layout == 'full' else 'col-12' %}
            <div class="row g-1">
                <div class="{{ mini_col }}"
                     style="background:#000; border-radius:4px; overflow:hidden;">
                    <canvas class="cv-explore d-block" width="256" height="256"
                            style="width:100%; image-rendering:pixelated;"></canvas>
                </div>
                <div class="{{ mini_col }}"
                     style="background:#000; border-radius:4px; overflow:hidden;">
                    <canvas class="cv-panoramic d-block" width="256" height="256"
                            style="width:100%; height:auto;"></canvas>
                </div>
                <div class="col-12 mt-1">
                    <div class="input-group input-group-sm">
                        <span class="input-group-text"
                              style="font-size:.65rem; padding:2px 6px;
                                     background:#1a1a1a; border-color:#333; color:#aaa;">Box</span>
                        <input type="number" class="ctrl-explore-w form-control form-control-sm"
                               value="70" step="10"
                               style="background:#111; border-color:#333;
                                      font-size:.7rem; color:#b0ffb0;">
                    </div>
                </div>
    <div class="row g-2 align-items-start">

        {# Left: main canvas + controls strip #}
        <div class="col-xl-9 col-lg-8">
            {{ _canvas() }}
            {{ _controls(panel_id, cam_id, has_loop) }}
        </div>

        {# Right: explore + panner + nested slot #}
        <div class="col-xl-3 col-lg-4 d-flex flex-column gap-2">
            {{ _mini(mini_col) }}
            {# Nested slot — filled by {% call %} in combo views #}
            {% if caller is defined %}
            <div class="viewer-nested-slot">{{ caller() }}</div>
            {% endif %}

        </div>

    </div>
+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ function initViewer(panelId, cam_id, hasLoop, apiBase) {

        fetch(`${apiBase}/${cam_id}/loop`)
            .then(r => r.json())
            .then(d => setLoopUI(d.running))
            .then(d => setLoopUI(d.response))
            .catch(() => {});
    }

+2 −2
Original line number Diff line number Diff line
@@ -150,12 +150,12 @@ document.addEventListener('DOMContentLoaded', () => {

    document.getElementById('btn-imaging-expose')?.addEventListener('click', () => {
        const form = document.getElementById('form-imaging');
        if (form) postExpose('snapshot', 'cam1', readForm(form));
        if (form) postExpose(MODES.station1.template, MODES.station1.camera, readForm(form));
    });

    document.getElementById('btn-spectro-expose')?.addEventListener('click', () => {
        const form = document.getElementById('form-spectro');
        if (form) postExpose('snapshot', 'cam2', readForm(form));
        if (form) postExpose(MODES.station2.template, MODES.station2.camera, readForm(form));
    });

    // -----------------------------------------------------------------------
Loading