DivYonko commited on
Commit
5ada189
·
1 Parent(s): 4929dfe

Move export button to sidebar above Danger Zone in both files

Browse files
Files changed (3) hide show
  1. app.py +66 -0
  2. frontend/streamlit_app.py +66 -71
  3. 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
+ &#128247; 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
- &#128247; 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
+ &#128247; 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
- &#128247; 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)