DivYonko commited on
Commit Β·
4929dfe
1
Parent(s): 6909867
Add screenshot PDF export button to frontend/streamlit_app.py stats view
Browse files- frontend/streamlit_app.py +71 -0
frontend/streamlit_app.py
CHANGED
|
@@ -2041,6 +2041,77 @@ elif len(st.session_state.streams) > 1:
|
|
| 2041 |
st.divider()
|
| 2042 |
st.info("Add video IDs to your extra stream slots and click βΆ Start to enable multi-stream comparison.")
|
| 2043 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2044 |
# ββ AUTO REFRESH ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2045 |
if auto_refresh:
|
| 2046 |
time.sleep(refresh_rate)
|
|
|
|
| 2041 |
st.divider()
|
| 2042 |
st.info("Add video IDs to your extra stream slots and click βΆ Start to enable multi-stream comparison.")
|
| 2043 |
|
| 2044 |
+
# ββ SCREENSHOT / PDF EXPORT βββββββββββββββββββββββββββββββββββ
|
| 2045 |
+
st.divider()
|
| 2046 |
+
st.markdown(
|
| 2047 |
+
'<div class="sec-hdr"><span class="sec-ttl">Export</span><span class="sec-pill">Stats Page</span></div>',
|
| 2048 |
+
unsafe_allow_html=True
|
| 2049 |
+
)
|
| 2050 |
+
|
| 2051 |
+
import streamlit.components.v1 as _components
|
| 2052 |
+
_components.html("""
|
| 2053 |
+
<div style="text-align:center; padding: 8px 0;">
|
| 2054 |
+
<button id="screenshotBtn" style="
|
| 2055 |
+
background: linear-gradient(135deg, #7c3aed, #4f46e5);
|
| 2056 |
+
color: #fff; border: none; border-radius: 10px;
|
| 2057 |
+
padding: 10px 28px; font-size: 14px; font-weight: 600;
|
| 2058 |
+
font-family: 'Space Grotesk', sans-serif; cursor: pointer;
|
| 2059 |
+
box-shadow: 0 4px 16px rgba(124,58,237,0.4); transition: transform 0.2s;"
|
| 2060 |
+
onmouseover="this.style.transform='translateY(-2px)'"
|
| 2061 |
+
onmouseout="this.style.transform='translateY(0)'"
|
| 2062 |
+
onclick="captureAndDownload()">
|
| 2063 |
+
📷 Download Stats as PDF
|
| 2064 |
+
</button>
|
| 2065 |
+
<div id="statusMsg" style="margin-top:8px; font-size:12px; color:#94a3b8;"></div>
|
| 2066 |
+
</div>
|
| 2067 |
+
<script>
|
| 2068 |
+
async function captureAndDownload() {
|
| 2069 |
+
const btn = document.getElementById('screenshotBtn');
|
| 2070 |
+
const msg = document.getElementById('statusMsg');
|
| 2071 |
+
btn.disabled = true; btn.textContent = 'Capturing...';
|
| 2072 |
+
msg.textContent = 'Please wait...';
|
| 2073 |
+
try {
|
| 2074 |
+
const target = window.parent.document.querySelector('[data-testid="stMain"]')
|
| 2075 |
+
|| window.parent.document.querySelector('.main')
|
| 2076 |
+
|| window.parent.document.body;
|
| 2077 |
+
const canvas = await window.parent.html2canvas(target, {
|
| 2078 |
+
scale: 1.5, useCORS: true, allowTaint: true,
|
| 2079 |
+
backgroundColor: '#07070f', logging: false,
|
| 2080 |
+
windowWidth: target.scrollWidth, windowHeight: target.scrollHeight,
|
| 2081 |
+
scrollX: 0, scrollY: 0,
|
| 2082 |
+
});
|
| 2083 |
+
const imgData = canvas.toDataURL('image/png', 0.95);
|
| 2084 |
+
const { jsPDF } = window.parent.jspdf;
|
| 2085 |
+
const pdf = new jsPDF({
|
| 2086 |
+
orientation: canvas.width > canvas.height ? 'l' : 'p',
|
| 2087 |
+
unit: 'px', format: [canvas.width, canvas.height], compress: true,
|
| 2088 |
+
});
|
| 2089 |
+
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
|
| 2090 |
+
const ts = new Date().toISOString().slice(0,16).replace('T','_').replace(':','-');
|
| 2091 |
+
pdf.save('livepulse_stats_' + ts + '.pdf');
|
| 2092 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 2093 |
+
msg.textContent = 'Done! Check your downloads.';
|
| 2094 |
+
setTimeout(() => { msg.textContent = ''; }, 4000);
|
| 2095 |
+
} catch(e) {
|
| 2096 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 2097 |
+
msg.textContent = 'Error: ' + e.message;
|
| 2098 |
+
}
|
| 2099 |
+
}
|
| 2100 |
+
function loadScript(src, globalName) {
|
| 2101 |
+
return new Promise((resolve) => {
|
| 2102 |
+
if (window.parent[globalName]) { resolve(); return; }
|
| 2103 |
+
const s = window.parent.document.createElement('script');
|
| 2104 |
+
s.src = src; s.onload = resolve;
|
| 2105 |
+
window.parent.document.head.appendChild(s);
|
| 2106 |
+
});
|
| 2107 |
+
}
|
| 2108 |
+
(async () => {
|
| 2109 |
+
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', 'html2canvas');
|
| 2110 |
+
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', 'jspdf');
|
| 2111 |
+
})();
|
| 2112 |
+
</script>
|
| 2113 |
+
""", height=90)
|
| 2114 |
+
|
| 2115 |
# ββ AUTO REFRESH ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2116 |
if auto_refresh:
|
| 2117 |
time.sleep(refresh_rate)
|