dataforth/testdatadb UI: fix cert fit (transform-scale) + publish-state chips
- fitCert: replace the flaky CSS `zoom` (Firefox support is recent/inconsistent) with transform:scale() measured against the widest line (+ right margin and font-load retries) so the cert always scales to fit the inspector with no horizontal clip. Validated live on a narrow 5B cert (0.74x) and a wide DSCA45 cert (0.55x) against the real AD2 dataset. - inspector Web field -> Published (green) / Not published (amber) chips. - widen default inspector 480 -> 500px. - tools/preview-proxy.py: serve the prototype AND reverse-proxy /api to the live AD2 server so the cert iframe is same-origin during preview — styleCert/fitCert read iframe.contentDocument, which silently no-ops when the iframe is loaded cross-origin straight from AD2 (why the fit looked broken in earlier previews). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
--hover:#f1f5f9; --sel:#e0e7ff; --desk:#e7ecf1;
|
||||
--mono:ui-monospace,"SFMono-Regular",Consolas,"Liberation Mono",monospace;
|
||||
--sans:Inter,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||||
--r:6px; --insp:480px;
|
||||
--r:6px; --insp:500px;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html,body{height:100%;margin:0}
|
||||
@@ -112,6 +112,10 @@
|
||||
.pill{display:inline-block;font-size:10.5px;font-weight:700;padding:1px 8px;border-radius:4px;font-family:var(--mono)}
|
||||
.pill.PASS{background:var(--pass-bg);color:var(--pass-ink)}
|
||||
.pill.FAIL{background:var(--fail-bg);color:var(--fail-ink)}
|
||||
.tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:2px 8px;border-radius:20px;font-family:var(--sans)}
|
||||
.tag::before{content:"";width:7px;height:7px;border-radius:50%}
|
||||
.tag.pub{background:var(--pass-bg);color:var(--pass-ink)} .tag.pub::before{background:var(--pass-ink)}
|
||||
.tag.unpub{background:#fef3c7;color:#92400e} .tag.unpub::before{background:#d97706;background:none;box-shadow:inset 0 0 0 1.5px #d97706}
|
||||
.web{font-size:13px}
|
||||
.pager{display:flex;align-items:center;gap:10px;padding:7px 12px;border-top:1px solid var(--border);font-size:12px;color:var(--ink-2)}
|
||||
.pager button{font-size:12px;height:28px;padding:0 11px;border:1px solid var(--border-strong);border-radius:var(--r);background:#fff;cursor:pointer;color:var(--ink)}
|
||||
@@ -321,7 +325,7 @@ function select(id,auto){
|
||||
<dt>Result</dt><dd><span class="pill ${r.overall_result}">${esc(r.overall_result)}</span></dd>
|
||||
<dt>Log</dt><dd>${esc(r.log_type)}</dd>
|
||||
${r.work_order?`<dt>WO</dt><dd>${esc(r.work_order)}</dd>`:''}
|
||||
<dt>Web</dt><dd>${r.api_uploaded_at?'published '+fmtDate(r.api_uploaded_at):'not published'}</dd></dl>`;
|
||||
<dt>Web</dt><dd>${r.api_uploaded_at?`<span class="tag pub">Published</span> <span style="color:var(--ink-3)">${fmtDate(r.api_uploaded_at)}</span>`:'<span class="tag unpub">Not published</span>'}</dd></dl>`;
|
||||
const ds=API+'/api/datasheet/'+id;
|
||||
$('acts').style.display='flex';
|
||||
$('acts').innerHTML=`<a class="pri" href="${ds}?format=html" target="_blank">Open ↗</a>
|
||||
@@ -338,18 +342,23 @@ function select(id,auto){
|
||||
}
|
||||
function loadCert(ds){
|
||||
$('viewer').innerHTML='<iframe id="dsframe" title="datasheet"></iframe>';
|
||||
const f=$('dsframe'); f.onload=()=>{styleCert();fitCert();}; f.src=ds+'?format=html';
|
||||
const f=$('dsframe'); f.onload=()=>{styleCert();fitCert();setTimeout(fitCert,120);setTimeout(fitCert,350);}; f.src=ds+'?format=html';
|
||||
}
|
||||
function styleCert(){ const f=$('dsframe'); try{ const doc=f.contentDocument; if(!doc)return;
|
||||
if(!doc.getElementById('_inj')){ const s=doc.createElement('style'); s.id='_inj';
|
||||
s.textContent='html,body{background:#fff!important;margin:0!important}body{padding:22px 26px!important;color:#0f172a}pre{margin:0;font-family:'+getComputedStyle(document.body).getPropertyValue('--mono')+';font-size:12.5px;line-height:1.32}';
|
||||
s.textContent='html,body{background:#fff!important;margin:0!important}body{padding:16px 20px!important;color:#0f172a}pre{margin:0;font-family:'+getComputedStyle(document.body).getPropertyValue('--mono')+';font-size:12.5px;line-height:1.32}';
|
||||
(doc.head||doc.documentElement).appendChild(s); }
|
||||
}catch(e){} }
|
||||
function fitCert(){ const f=$('dsframe'); if(!f)return; try{ const doc=f.contentDocument; if(!doc)return;
|
||||
const root=doc.documentElement; root.style.zoom='';
|
||||
const nat=Math.max(doc.body?doc.body.scrollWidth:0,root.scrollWidth), av=f.clientWidth-2;
|
||||
root.style.zoom=(nat>av)?Math.max(.45,av/nat):1;
|
||||
f.style.height=Math.ceil((doc.body?doc.body.scrollHeight:600)*(nat>av?av/nat:1)+4)+'px';
|
||||
function fitCert(){ const f=$('dsframe'); if(!f)return; try{ const doc=f.contentDocument; if(!doc||!doc.body)return;
|
||||
const b=doc.body, root=doc.documentElement;
|
||||
// measure natural content width (transform doesn't reflow, so text never rewraps)
|
||||
b.style.transform='none'; b.style.width='max-content'; b.style.transformOrigin='0 0';
|
||||
root.style.overflow='hidden';
|
||||
const nat=Math.max(b.scrollWidth,root.scrollWidth), av=f.clientWidth-12; // widest line + right margin
|
||||
if(!nat){ return; } // not laid out yet — the retry will catch it
|
||||
const k=nat>av ? Math.max(.4, av/nat) : 1;
|
||||
b.style.transform='scale('+k+')';
|
||||
f.style.height=Math.ceil(b.scrollHeight*k+2)+'px'; // size the frame to the scaled cert, no v-scroll-in-frame
|
||||
}catch(e){} }
|
||||
function printCert(){ const f=$('dsframe'); if(f&&f.contentWindow){f.contentWindow.focus();f.contentWindow.print();} }
|
||||
window.addEventListener('resize',()=>{fitCert();});
|
||||
|
||||
54
projects/dataforth-dos/tools/preview-proxy.py
Normal file
54
projects/dataforth-dos/tools/preview-proxy.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Same-origin preview proxy for the testdatadb front-end.
|
||||
|
||||
Serves the static prototype from ROOT and reverse-proxies /api/* to the live
|
||||
AD2 testdatadb server, so the app AND the cert iframe share one origin
|
||||
(http://127.0.0.1:PORT). That lets the same-origin-only cert styling/fit logic
|
||||
(styleCert/fitCert read iframe.contentDocument) actually run during preview —
|
||||
which it can't when the iframe is loaded cross-origin straight from AD2.
|
||||
|
||||
Usage: python preview-proxy.py <port> <root-dir> [target]
|
||||
"""
|
||||
import http.server, socketserver, urllib.request, urllib.error, os, sys
|
||||
|
||||
PORT = int(sys.argv[1])
|
||||
ROOT = os.path.abspath(sys.argv[2]) if len(sys.argv) > 2 else "."
|
||||
TARGET = sys.argv[3] if len(sys.argv) > 3 else "http://192.168.0.6:3000"
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path.startswith("/api/"):
|
||||
try:
|
||||
with urllib.request.urlopen(TARGET + self.path, timeout=30) as r:
|
||||
body = r.read(); ct = r.headers.get("Content-Type", "application/octet-stream")
|
||||
self._send(200, ct, body)
|
||||
except urllib.error.HTTPError as e:
|
||||
self._send(e.code, "application/json", e.read())
|
||||
except Exception as e:
|
||||
self._send(502, "text/plain", str(e).encode())
|
||||
else:
|
||||
p = self.path.split("?")[0]
|
||||
p = "/index.html" if p == "/" else p
|
||||
fp = os.path.join(ROOT, p.lstrip("/"))
|
||||
if os.path.isfile(fp):
|
||||
ct = "text/html; charset=utf-8" if fp.endswith(".html") else "application/octet-stream"
|
||||
self._send(200, ct, open(fp, "rb").read())
|
||||
else:
|
||||
self._send(404, "text/plain", b"not found")
|
||||
|
||||
def _send(self, code, ct, body):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", ct)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, *a): pass
|
||||
|
||||
class S(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
print(f"preview proxy: http://127.0.0.1:{PORT} root={ROOT} -> {TARGET}")
|
||||
S(("127.0.0.1", PORT), H).serve_forever()
|
||||
Reference in New Issue
Block a user