DivYonko commited on
Commit ·
5ada189
1
Parent(s): 4929dfe
Move export button to sidebar above Danger Zone in both files
Browse files- app.py +66 -0
- frontend/streamlit_app.py +66 -71
- pages/stats.py +0 -102
app.py
CHANGED
|
@@ -1098,6 +1098,72 @@ with st.sidebar:
|
|
| 1098 |
st.markdown('<div style="font-size:0.72rem;color:var(--text-3);">No active streams</div>', unsafe_allow_html=True)
|
| 1099 |
st.divider()
|
| 1100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1101 |
# -- Danger Zone --
|
| 1102 |
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:#ef4444;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Danger Zone</p>', unsafe_allow_html=True)
|
| 1103 |
if st.button("\U0001f5d1 Clear all data"):
|
|
|
|
| 1098 |
st.markdown('<div style="font-size:0.72rem;color:var(--text-3);">No active streams</div>', unsafe_allow_html=True)
|
| 1099 |
st.divider()
|
| 1100 |
|
| 1101 |
+
# -- Export --
|
| 1102 |
+
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Export</p>', unsafe_allow_html=True)
|
| 1103 |
+
import streamlit.components.v1 as _comp
|
| 1104 |
+
_comp.html("""
|
| 1105 |
+
<div style="padding:2px 0;">
|
| 1106 |
+
<button id="sidebarScreenshotBtn" style="
|
| 1107 |
+
width:100%; background:linear-gradient(135deg,#7c3aed,#4f46e5);
|
| 1108 |
+
color:#fff; border:none; border-radius:10px; padding:8px 12px;
|
| 1109 |
+
font-size:13px; font-weight:600; cursor:pointer;
|
| 1110 |
+
box-shadow:0 4px 16px rgba(124,58,237,0.3); transition:transform 0.2s;"
|
| 1111 |
+
onmouseover="this.style.transform='translateY(-2px)'"
|
| 1112 |
+
onmouseout="this.style.transform='translateY(0)'"
|
| 1113 |
+
onclick="sidebarCapture()">
|
| 1114 |
+
📷 Download Stats as PDF
|
| 1115 |
+
</button>
|
| 1116 |
+
<div id="sidebarMsg" style="margin-top:6px;font-size:11px;color:#94a3b8;text-align:center;"></div>
|
| 1117 |
+
</div>
|
| 1118 |
+
<script>
|
| 1119 |
+
async function sidebarCapture() {
|
| 1120 |
+
const btn = document.getElementById('sidebarScreenshotBtn');
|
| 1121 |
+
const msg = document.getElementById('sidebarMsg');
|
| 1122 |
+
btn.disabled = true; btn.textContent = 'Capturing...';
|
| 1123 |
+
msg.textContent = 'Please wait...';
|
| 1124 |
+
try {
|
| 1125 |
+
const target = window.parent.document.querySelector('[data-testid="stMain"]')
|
| 1126 |
+
|| window.parent.document.querySelector('.main')
|
| 1127 |
+
|| window.parent.document.body;
|
| 1128 |
+
const canvas = await window.parent.html2canvas(target, {
|
| 1129 |
+
scale:1.5, useCORS:true, allowTaint:true,
|
| 1130 |
+
backgroundColor:'#07070f', logging:false,
|
| 1131 |
+
windowWidth:target.scrollWidth, windowHeight:target.scrollHeight,
|
| 1132 |
+
scrollX:0, scrollY:0,
|
| 1133 |
+
});
|
| 1134 |
+
const imgData = canvas.toDataURL('image/png', 0.95);
|
| 1135 |
+
const { jsPDF } = window.parent.jspdf;
|
| 1136 |
+
const pdf = new jsPDF({
|
| 1137 |
+
orientation: canvas.width > canvas.height ? 'l' : 'p',
|
| 1138 |
+
unit:'px', format:[canvas.width, canvas.height], compress:true,
|
| 1139 |
+
});
|
| 1140 |
+
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
|
| 1141 |
+
const ts = new Date().toISOString().slice(0,16).replace('T','_').replace(':','-');
|
| 1142 |
+
pdf.save('livepulse_stats_' + ts + '.pdf');
|
| 1143 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 1144 |
+
msg.textContent = 'Done!';
|
| 1145 |
+
setTimeout(() => { msg.textContent = ''; }, 3000);
|
| 1146 |
+
} catch(e) {
|
| 1147 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 1148 |
+
msg.textContent = 'Error: ' + e.message;
|
| 1149 |
+
}
|
| 1150 |
+
}
|
| 1151 |
+
function loadScript(src, name) {
|
| 1152 |
+
return new Promise(r => {
|
| 1153 |
+
if (window.parent[name]) { r(); return; }
|
| 1154 |
+
const s = window.parent.document.createElement('script');
|
| 1155 |
+
s.src = src; s.onload = r;
|
| 1156 |
+
window.parent.document.head.appendChild(s);
|
| 1157 |
+
});
|
| 1158 |
+
}
|
| 1159 |
+
(async () => {
|
| 1160 |
+
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js','html2canvas');
|
| 1161 |
+
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js','jspdf');
|
| 1162 |
+
})();
|
| 1163 |
+
</script>
|
| 1164 |
+
""", height=75)
|
| 1165 |
+
st.divider()
|
| 1166 |
+
|
| 1167 |
# -- Danger Zone --
|
| 1168 |
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:#ef4444;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Danger Zone</p>', unsafe_allow_html=True)
|
| 1169 |
if st.button("\U0001f5d1 Clear all data"):
|
frontend/streamlit_app.py
CHANGED
|
@@ -759,6 +759,72 @@ with st.sidebar:
|
|
| 759 |
st.markdown('<div style="font-size:0.72rem;color:var(--text-3);">No active streams</div>', unsafe_allow_html=True)
|
| 760 |
st.divider()
|
| 761 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 762 |
# ── Danger Zone ──
|
| 763 |
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:#ef4444;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Danger Zone</p>', unsafe_allow_html=True)
|
| 764 |
if st.button("🗑 Clear all data", width='stretch'):
|
|
@@ -2041,77 +2107,6 @@ 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 |
-
# ── 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)
|
|
|
|
| 759 |
st.markdown('<div style="font-size:0.72rem;color:var(--text-3);">No active streams</div>', unsafe_allow_html=True)
|
| 760 |
st.divider()
|
| 761 |
|
| 762 |
+
# ── Export ──
|
| 763 |
+
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:var(--accent);text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Export</p>', unsafe_allow_html=True)
|
| 764 |
+
import streamlit.components.v1 as _comp2
|
| 765 |
+
_comp2.html("""
|
| 766 |
+
<div style="padding:2px 0;">
|
| 767 |
+
<button id="sidebarScreenshotBtn2" style="
|
| 768 |
+
width:100%; background:linear-gradient(135deg,#7c3aed,#4f46e5);
|
| 769 |
+
color:#fff; border:none; border-radius:10px; padding:8px 12px;
|
| 770 |
+
font-size:13px; font-weight:600; cursor:pointer;
|
| 771 |
+
box-shadow:0 4px 16px rgba(124,58,237,0.3); transition:transform 0.2s;"
|
| 772 |
+
onmouseover="this.style.transform='translateY(-2px)'"
|
| 773 |
+
onmouseout="this.style.transform='translateY(0)'"
|
| 774 |
+
onclick="sidebarCapture2()">
|
| 775 |
+
📷 Download Stats as PDF
|
| 776 |
+
</button>
|
| 777 |
+
<div id="sidebarMsg2" style="margin-top:6px;font-size:11px;color:#94a3b8;text-align:center;"></div>
|
| 778 |
+
</div>
|
| 779 |
+
<script>
|
| 780 |
+
async function sidebarCapture2() {
|
| 781 |
+
const btn = document.getElementById('sidebarScreenshotBtn2');
|
| 782 |
+
const msg = document.getElementById('sidebarMsg2');
|
| 783 |
+
btn.disabled = true; btn.textContent = 'Capturing...';
|
| 784 |
+
msg.textContent = 'Please wait...';
|
| 785 |
+
try {
|
| 786 |
+
const target = window.parent.document.querySelector('[data-testid="stMain"]')
|
| 787 |
+
|| window.parent.document.querySelector('.main')
|
| 788 |
+
|| window.parent.document.body;
|
| 789 |
+
const canvas = await window.parent.html2canvas(target, {
|
| 790 |
+
scale:1.5, useCORS:true, allowTaint:true,
|
| 791 |
+
backgroundColor:'#07070f', logging:false,
|
| 792 |
+
windowWidth:target.scrollWidth, windowHeight:target.scrollHeight,
|
| 793 |
+
scrollX:0, scrollY:0,
|
| 794 |
+
});
|
| 795 |
+
const imgData = canvas.toDataURL('image/png', 0.95);
|
| 796 |
+
const { jsPDF } = window.parent.jspdf;
|
| 797 |
+
const pdf = new jsPDF({
|
| 798 |
+
orientation: canvas.width > canvas.height ? 'l' : 'p',
|
| 799 |
+
unit:'px', format:[canvas.width, canvas.height], compress:true,
|
| 800 |
+
});
|
| 801 |
+
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
|
| 802 |
+
const ts = new Date().toISOString().slice(0,16).replace('T','_').replace(':','-');
|
| 803 |
+
pdf.save('livepulse_stats_' + ts + '.pdf');
|
| 804 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 805 |
+
msg.textContent = 'Done!';
|
| 806 |
+
setTimeout(() => { msg.textContent = ''; }, 3000);
|
| 807 |
+
} catch(e) {
|
| 808 |
+
btn.textContent = 'Download Stats as PDF'; btn.disabled = false;
|
| 809 |
+
msg.textContent = 'Error: ' + e.message;
|
| 810 |
+
}
|
| 811 |
+
}
|
| 812 |
+
function loadScript2(src, name) {
|
| 813 |
+
return new Promise(r => {
|
| 814 |
+
if (window.parent[name]) { r(); return; }
|
| 815 |
+
const s = window.parent.document.createElement('script');
|
| 816 |
+
s.src = src; s.onload = r;
|
| 817 |
+
window.parent.document.head.appendChild(s);
|
| 818 |
+
});
|
| 819 |
+
}
|
| 820 |
+
(async () => {
|
| 821 |
+
await loadScript2('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js','html2canvas');
|
| 822 |
+
await loadScript2('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js','jspdf');
|
| 823 |
+
})();
|
| 824 |
+
</script>
|
| 825 |
+
""", height=75)
|
| 826 |
+
st.divider()
|
| 827 |
+
|
| 828 |
# ── Danger Zone ──
|
| 829 |
st.markdown('<p style="font-size:0.68rem;font-weight:700;color:#ef4444;text-transform:uppercase;letter-spacing:0.1em;margin-bottom:8px;">Danger Zone</p>', unsafe_allow_html=True)
|
| 830 |
if st.button("🗑 Clear all data", width='stretch'):
|
|
|
|
| 2107 |
st.divider()
|
| 2108 |
st.info("Add video IDs to your extra stream slots and click ▶ Start to enable multi-stream comparison.")
|
| 2109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2110 |
# ── AUTO REFRESH ──────────────────────────────────────────────
|
| 2111 |
if auto_refresh:
|
| 2112 |
time.sleep(refresh_rate)
|
pages/stats.py
CHANGED
|
@@ -976,108 +976,6 @@ elif len(st.session_state.streams) > 1:
|
|
| 976 |
st.divider()
|
| 977 |
st.info("Add video IDs to your extra stream slots and click ▶ Start to enable multi-stream comparison.")
|
| 978 |
|
| 979 |
-
# -- SCREENSHOT / PDF EXPORT ----------------------------------
|
| 980 |
-
st.divider()
|
| 981 |
-
st.markdown(
|
| 982 |
-
'<div class="sec-hdr"><span class="sec-ttl">Export</span><span class="sec-pill">Stats Page</span></div>',
|
| 983 |
-
unsafe_allow_html=True
|
| 984 |
-
)
|
| 985 |
-
|
| 986 |
-
import streamlit.components.v1 as components
|
| 987 |
-
components.html("""
|
| 988 |
-
<div style="text-align:center; padding: 8px 0;">
|
| 989 |
-
<button id="screenshotBtn" style="
|
| 990 |
-
background: linear-gradient(135deg, #7c3aed, #4f46e5);
|
| 991 |
-
color: #fff;
|
| 992 |
-
border: none;
|
| 993 |
-
border-radius: 10px;
|
| 994 |
-
padding: 10px 28px;
|
| 995 |
-
font-size: 14px;
|
| 996 |
-
font-weight: 600;
|
| 997 |
-
font-family: 'Space Grotesk', sans-serif;
|
| 998 |
-
cursor: pointer;
|
| 999 |
-
box-shadow: 0 4px 16px rgba(124,58,237,0.4);
|
| 1000 |
-
transition: transform 0.2s;
|
| 1001 |
-
"
|
| 1002 |
-
onmouseover="this.style.transform='translateY(-2px)'"
|
| 1003 |
-
onmouseout="this.style.transform='translateY(0)'"
|
| 1004 |
-
onclick="captureAndDownload()">
|
| 1005 |
-
📷 Download Stats as PDF
|
| 1006 |
-
</button>
|
| 1007 |
-
<div id="statusMsg" style="margin-top:8px; font-size:12px; color:#94a3b8;"></div>
|
| 1008 |
-
</div>
|
| 1009 |
-
|
| 1010 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 1011 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
| 1012 |
-
<script>
|
| 1013 |
-
async function captureAndDownload() {
|
| 1014 |
-
const btn = document.getElementById('screenshotBtn');
|
| 1015 |
-
const msg = document.getElementById('statusMsg');
|
| 1016 |
-
btn.disabled = true;
|
| 1017 |
-
btn.textContent = 'Capturing...';
|
| 1018 |
-
msg.textContent = 'Please wait, capturing the page...';
|
| 1019 |
-
|
| 1020 |
-
try {
|
| 1021 |
-
// Target the main Streamlit content area in the parent frame
|
| 1022 |
-
const target = window.parent.document.querySelector('[data-testid="stMain"]')
|
| 1023 |
-
|| window.parent.document.querySelector('.main')
|
| 1024 |
-
|| window.parent.document.body;
|
| 1025 |
-
|
| 1026 |
-
const canvas = await window.parent.html2canvas(target, {
|
| 1027 |
-
scale: 1.5,
|
| 1028 |
-
useCORS: true,
|
| 1029 |
-
allowTaint: true,
|
| 1030 |
-
backgroundColor: '#07070f',
|
| 1031 |
-
logging: false,
|
| 1032 |
-
windowWidth: target.scrollWidth,
|
| 1033 |
-
windowHeight: target.scrollHeight,
|
| 1034 |
-
scrollX: 0,
|
| 1035 |
-
scrollY: 0,
|
| 1036 |
-
});
|
| 1037 |
-
|
| 1038 |
-
const imgData = canvas.toDataURL('image/png', 0.95);
|
| 1039 |
-
const { jsPDF } = window.parent.jspdf;
|
| 1040 |
-
const pdf = new jsPDF({
|
| 1041 |
-
orientation: canvas.width > canvas.height ? 'l' : 'p',
|
| 1042 |
-
unit: 'px',
|
| 1043 |
-
format: [canvas.width, canvas.height],
|
| 1044 |
-
compress: true,
|
| 1045 |
-
});
|
| 1046 |
-
|
| 1047 |
-
pdf.addImage(imgData, 'PNG', 0, 0, canvas.width, canvas.height);
|
| 1048 |
-
const ts = new Date().toISOString().slice(0,16).replace('T','_').replace(':','-');
|
| 1049 |
-
pdf.save('livepulse_stats_' + ts + '.pdf');
|
| 1050 |
-
|
| 1051 |
-
btn.textContent = 'Download Stats as PDF';
|
| 1052 |
-
btn.disabled = false;
|
| 1053 |
-
msg.textContent = 'Done! Check your downloads.';
|
| 1054 |
-
setTimeout(() => { msg.textContent = ''; }, 4000);
|
| 1055 |
-
} catch(e) {
|
| 1056 |
-
btn.textContent = 'Download Stats as PDF';
|
| 1057 |
-
btn.disabled = false;
|
| 1058 |
-
msg.textContent = 'Error: ' + e.message;
|
| 1059 |
-
console.error(e);
|
| 1060 |
-
}
|
| 1061 |
-
}
|
| 1062 |
-
|
| 1063 |
-
// Load libraries into parent frame if not already there
|
| 1064 |
-
function loadScript(src, globalName) {
|
| 1065 |
-
return new Promise((resolve) => {
|
| 1066 |
-
if (window.parent[globalName]) { resolve(); return; }
|
| 1067 |
-
const s = window.parent.document.createElement('script');
|
| 1068 |
-
s.src = src;
|
| 1069 |
-
s.onload = resolve;
|
| 1070 |
-
window.parent.document.head.appendChild(s);
|
| 1071 |
-
});
|
| 1072 |
-
}
|
| 1073 |
-
|
| 1074 |
-
(async () => {
|
| 1075 |
-
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', 'html2canvas');
|
| 1076 |
-
await loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js', 'jspdf');
|
| 1077 |
-
})();
|
| 1078 |
-
</script>
|
| 1079 |
-
""", height=90)
|
| 1080 |
-
|
| 1081 |
# -- AUTO REFRESH ---------------------------------------------
|
| 1082 |
if auto_refresh:
|
| 1083 |
time.sleep(refresh_rate)
|
|
|
|
| 976 |
st.divider()
|
| 977 |
st.info("Add video IDs to your extra stream slots and click ▶ Start to enable multi-stream comparison.")
|
| 978 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 979 |
# -- AUTO REFRESH ---------------------------------------------
|
| 980 |
if auto_refresh:
|
| 981 |
time.sleep(refresh_rate)
|