diff options
| author | kj_sh604 | 2026-02-14 00:16:21 -0500 |
|---|---|---|
| committer | kj_sh604 | 2026-02-14 00:16:21 -0500 |
| commit | 5d527cb99cd733f02db25ac4666e817699a8a5a0 (patch) | |
| tree | f03af296a449e2f3aee372c0d20edd979e0e64a5 | |
| parent | d44a2f72e988d423a7ad6531f6779aff82edc848 (diff) | |
refactor: minify inline css and js
might regret this, but I probably won't iterate on the demoware on the public repo
| -rw-r--r-- | demoware/index.html | 249 |
1 files changed, 2 insertions, 247 deletions
diff --git a/demoware/index.html b/demoware/index.html index 95c6847..ce93e4e 100644 --- a/demoware/index.html +++ b/demoware/index.html | |||
| @@ -6,49 +6,7 @@ | |||
| 6 | <title>slide merging demoware</title> | 6 | <title>slide merging demoware</title> |
| 7 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css"> | 7 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css"> |
| 8 | <style> | 8 | <style> |
| 9 | .file-item { | 9 | .file-item { display: flex; align-items: center; gap: 0.5rem; padding: 0.3rem 0; } .file-item button { padding: 0.15rem 0.5rem; margin: 0; font-size: 0.85rem; min-width: 2rem; } .file-item .fname { flex: 1; font-family: var(--font-family, monospace); } #drop-zone { border: 2px dashed #555; padding: 2rem; text-align: center; border-radius: 6px; margin: 1rem 0; cursor: pointer; transition: border-color 0.15s, background 0.15s; } #drop-zone.over { border-color: #7ec8e3; background: rgba(126, 200, 227, 0.06); } #command-display { white-space: pre-wrap; word-break: break-all; } .hidden { display: none !important; } .running { color: #ff9800; } .done { color: #4caf50; } .error { color: #f44336; } #log-output { max-height: 300px; overflow-y: auto; font-size: 0.85rem; } footer { margin-top: 2rem; } |
| 10 | display: flex; | ||
| 11 | align-items: center; | ||
| 12 | gap: 0.5rem; | ||
| 13 | padding: 0.3rem 0; | ||
| 14 | } | ||
| 15 | .file-item button { | ||
| 16 | padding: 0.15rem 0.5rem; | ||
| 17 | margin: 0; | ||
| 18 | font-size: 0.85rem; | ||
| 19 | min-width: 2rem; | ||
| 20 | } | ||
| 21 | .file-item .fname { | ||
| 22 | flex: 1; | ||
| 23 | font-family: var(--font-family, monospace); | ||
| 24 | } | ||
| 25 | #drop-zone { | ||
| 26 | border: 2px dashed #555; | ||
| 27 | padding: 2rem; | ||
| 28 | text-align: center; | ||
| 29 | border-radius: 6px; | ||
| 30 | margin: 1rem 0; | ||
| 31 | cursor: pointer; | ||
| 32 | transition: border-color 0.15s, background 0.15s; | ||
| 33 | } | ||
| 34 | #drop-zone.over { | ||
| 35 | border-color: #7ec8e3; | ||
| 36 | background: rgba(126, 200, 227, 0.06); | ||
| 37 | } | ||
| 38 | #command-display { | ||
| 39 | white-space: pre-wrap; | ||
| 40 | word-break: break-all; | ||
| 41 | } | ||
| 42 | .hidden { display: none !important; } | ||
| 43 | .running { color: #ff9800; } | ||
| 44 | .done { color: #4caf50; } | ||
| 45 | .error { color: #f44336; } | ||
| 46 | #log-output { | ||
| 47 | max-height: 300px; | ||
| 48 | overflow-y: auto; | ||
| 49 | font-size: 0.85rem; | ||
| 50 | } | ||
| 51 | footer { margin-top: 2rem; } | ||
| 52 | </style> | 10 | </style> |
| 53 | </head> | 11 | </head> |
| 54 | <body> | 12 | <body> |
| @@ -88,210 +46,7 @@ | |||
| 88 | </footer> | 46 | </footer> |
| 89 | 47 | ||
| 90 | <script> | 48 | <script> |
| 91 | (() => { | 49 | (()=>{"use strict";let e=[],t=1,n=!1;const d=document.getElementById("drop-zone"),a=document.getElementById("file-input"),o=document.getElementById("file-list"),s=document.getElementById("empty-msg"),i=document.getElementById("merge-btn"),r=document.getElementById("cmd-section"),l=document.getElementById("command-display"),c=document.getElementById("status-section"),m=document.getElementById("status-msg"),u=document.getElementById("log-output"),g=document.getElementById("dl-section"),h=document.getElementById("dl-link");function p(n){for(const d of n)d.name.toLowerCase().endsWith(".pptx")&&e.push({id:t++,name:d.name,file:d});f()}function f(){if(o.innerHTML="",0===e.length)return s.classList.remove("hidden"),void(i.disabled=!0);s.classList.add("hidden"),i.disabled=n,e.forEach(((t,n)=>{const d=document.createElement("div");d.className="file-item";const a=v("▲",0===n,(()=>y(n,n-1))),s=v("▼",n===e.length-1,(()=>y(n,n+1))),i=v("✕",!1,(()=>{e.splice(n,1),f()})),r=document.createElement("span");r.className="fname",r.textContent=n+1+". "+t.name,d.append(a,s,i,r),o.appendChild(d)}))}function v(e,t,n){const d=document.createElement("button");return d.textContent=e,d.disabled=t,d.onclick=n,d}function y(t,n){[e[t],e[n]]=[e[n],e[t]],f()}a.addEventListener("change",(()=>{p(a.files),a.value=""})),d.addEventListener("dragover",(e=>{e.preventDefault(),d.classList.add("over")})),d.addEventListener("dragleave",(()=>d.classList.remove("over"))),d.addEventListener("drop",(e=>{e.preventDefault(),d.classList.remove("over"),p(e.dataTransfer.files)})),d.addEventListener("click",(()=>a.click())),i.addEventListener("click",(async()=>{if(0===e.length||n)return;n=!0,i.disabled=!0,r.classList.add("hidden"),c.classList.add("hidden"),g.classList.add("hidden"),u.textContent="",u.classList.add("hidden");const t=Date.now()+"_"+Math.random().toString(36).substring(2,10);c.classList.remove("hidden");for(let d=0;d<e.length;d++){const a=e[d];m.textContent="uploading "+(d+1)+"/"+e.length+": "+a.name,m.className="running";const o=a.id+"_"+a.name;try{const e=await fetch("/api/upload",{method:"POST",headers:{"X-Job-Id":t,"X-Filename":encodeURIComponent(o)},body:a.file});if(!e.ok){const t=await e.json().catch((()=>({error:"upload failed"})));throw new Error(t.error)}}catch(e){return m.textContent="upload error: "+e.message,m.className="error",n=!1,void(i.disabled=!1)}}m.textContent="starting merge...";const d=e.map((e=>e.id+"_"+e.name));let a;try{const e=await fetch("/api/merge",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({job_id:t,files:d})});if(a=await e.json(),!e.ok)throw new Error(a.error||"merge failed")}catch(e){return m.textContent="merge error: "+e.message,m.className="error",n=!1,void(i.disabled=!1)}r.classList.remove("hidden"),l.textContent="$ "+a.command,m.textContent="merging... this may take a while depending on slide count.";const o=setInterval((async()=>{try{const e=await fetch("/api/status/"+t),d=await e.json();d.log&&(u.classList.remove("hidden"),u.textContent=d.log),"done"===d.status?(clearInterval(o),m.textContent="merge complete!",m.className="done",g.classList.remove("hidden"),h.href="/output/"+a.output,h.download=a.output,n=!1,i.disabled=!1):"error"===d.status&&(clearInterval(o),m.textContent="merge failed — check the log below.",m.className="error",n=!1,i.disabled=!1)}catch(e){}}),2e3)}))})(); |
| 92 | 'use strict'; | ||
| 93 | |||
| 94 | // -- state -- | ||
| 95 | let files = []; // {id, name, file} | ||
| 96 | let nextId = 1; | ||
| 97 | let busy = false; | ||
| 98 | |||
| 99 | // -- dom refs -- | ||
| 100 | const dropZone = document.getElementById('drop-zone'); | ||
| 101 | const fileInput = document.getElementById('file-input'); | ||
| 102 | const fileList = document.getElementById('file-list'); | ||
| 103 | const emptyMsg = document.getElementById('empty-msg'); | ||
| 104 | const mergeBtn = document.getElementById('merge-btn'); | ||
| 105 | const cmdSec = document.getElementById('cmd-section'); | ||
| 106 | const cmdPre = document.getElementById('command-display'); | ||
| 107 | const statusSec = document.getElementById('status-section'); | ||
| 108 | const statusMsg = document.getElementById('status-msg'); | ||
| 109 | const logPre = document.getElementById('log-output'); | ||
| 110 | const dlSec = document.getElementById('dl-section'); | ||
| 111 | const dlLink = document.getElementById('dl-link'); | ||
| 112 | |||
| 113 | // -- file input -- | ||
| 114 | fileInput.addEventListener('change', () => { | ||
| 115 | addFiles(fileInput.files); | ||
| 116 | fileInput.value = ''; | ||
| 117 | }); | ||
| 118 | |||
| 119 | // -- drag & drop -- | ||
| 120 | dropZone.addEventListener('dragover', e => { | ||
| 121 | e.preventDefault(); | ||
| 122 | dropZone.classList.add('over'); | ||
| 123 | }); | ||
| 124 | dropZone.addEventListener('dragleave', () => dropZone.classList.remove('over')); | ||
| 125 | dropZone.addEventListener('drop', e => { | ||
| 126 | e.preventDefault(); | ||
| 127 | dropZone.classList.remove('over'); | ||
| 128 | addFiles(e.dataTransfer.files); | ||
| 129 | }); | ||
| 130 | dropZone.addEventListener('click', () => fileInput.click()); | ||
| 131 | |||
| 132 | // -- list management -- | ||
| 133 | |||
| 134 | function addFiles(rawFiles) { | ||
| 135 | for (const f of rawFiles) { | ||
| 136 | if (!f.name.toLowerCase().endsWith('.pptx')) continue; | ||
| 137 | files.push({ id: nextId++, name: f.name, file: f }); | ||
| 138 | } | ||
| 139 | render(); | ||
| 140 | } | ||
| 141 | |||
| 142 | function render() { | ||
| 143 | fileList.innerHTML = ''; | ||
| 144 | |||
| 145 | if (files.length === 0) { | ||
| 146 | emptyMsg.classList.remove('hidden'); | ||
| 147 | mergeBtn.disabled = true; | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | |||
| 151 | emptyMsg.classList.add('hidden'); | ||
| 152 | mergeBtn.disabled = busy; | ||
| 153 | |||
| 154 | files.forEach((item, i) => { | ||
| 155 | const row = document.createElement('div'); | ||
| 156 | row.className = 'file-item'; | ||
| 157 | |||
| 158 | const up = mkbtn('▲', i === 0, () => swap(i, i - 1)); | ||
| 159 | const dn = mkbtn('▼', i === files.length - 1, () => swap(i, i + 1)); | ||
| 160 | const rm = mkbtn('✕', false, () => { files.splice(i, 1); render(); }); | ||
| 161 | |||
| 162 | const span = document.createElement('span'); | ||
| 163 | span.className = 'fname'; | ||
| 164 | span.textContent = (i + 1) + '. ' + item.name; | ||
| 165 | |||
| 166 | row.append(up, dn, rm, span); | ||
| 167 | fileList.appendChild(row); | ||
| 168 | }); | ||
| 169 | } | ||
| 170 | |||
| 171 | function mkbtn(label, disabled, onclick) { | ||
| 172 | const b = document.createElement('button'); | ||
| 173 | b.textContent = label; | ||
| 174 | b.disabled = disabled; | ||
| 175 | b.onclick = onclick; | ||
| 176 | return b; | ||
| 177 | } | ||
| 178 | |||
| 179 | function swap(a, b) { | ||
| 180 | [files[a], files[b]] = [files[b], files[a]]; | ||
| 181 | render(); | ||
| 182 | } | ||
| 183 | |||
| 184 | // -- merge flow -- | ||
| 185 | |||
| 186 | mergeBtn.addEventListener('click', async () => { | ||
| 187 | if (files.length === 0 || busy) return; | ||
| 188 | busy = true; | ||
| 189 | mergeBtn.disabled = true; | ||
| 190 | |||
| 191 | // reset ui sections | ||
| 192 | cmdSec.classList.add('hidden'); | ||
| 193 | statusSec.classList.add('hidden'); | ||
| 194 | dlSec.classList.add('hidden'); | ||
| 195 | logPre.textContent = ''; | ||
| 196 | logPre.classList.add('hidden'); | ||
| 197 | |||
| 198 | const jobId = Date.now() + '_' + rnd(); | ||
| 199 | |||
| 200 | statusSec.classList.remove('hidden'); | ||
| 201 | |||
| 202 | // 1. upload each file | ||
| 203 | for (let i = 0; i < files.length; i++) { | ||
| 204 | const item = files[i]; | ||
| 205 | statusMsg.textContent = 'uploading ' + (i + 1) + '/' + files.length + ': ' + item.name; | ||
| 206 | statusMsg.className = 'running'; | ||
| 207 | |||
| 208 | const serverName = item.id + '_' + item.name; | ||
| 209 | try { | ||
| 210 | const res = await fetch('/api/upload', { | ||
| 211 | method: 'POST', | ||
| 212 | headers: { | ||
| 213 | 'X-Job-Id': jobId, | ||
| 214 | 'X-Filename': encodeURIComponent(serverName), | ||
| 215 | }, | ||
| 216 | body: item.file, | ||
| 217 | }); | ||
| 218 | if (!res.ok) { | ||
| 219 | const err = await res.json().catch(() => ({ error: 'upload failed' })); | ||
| 220 | throw new Error(err.error); | ||
| 221 | } | ||
| 222 | } catch (e) { | ||
| 223 | statusMsg.textContent = 'upload error: ' + e.message; | ||
| 224 | statusMsg.className = 'error'; | ||
| 225 | busy = false; | ||
| 226 | mergeBtn.disabled = false; | ||
| 227 | return; | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | // 2. send merge request | ||
| 232 | statusMsg.textContent = 'starting merge...'; | ||
| 233 | const serverFiles = files.map(f => f.id + '_' + f.name); | ||
| 234 | |||
| 235 | let mergeData; | ||
| 236 | try { | ||
| 237 | const res = await fetch('/api/merge', { | ||
| 238 | method: 'POST', | ||
| 239 | headers: { 'Content-Type': 'application/json' }, | ||
| 240 | body: JSON.stringify({ job_id: jobId, files: serverFiles }), | ||
| 241 | }); | ||
| 242 | mergeData = await res.json(); | ||
| 243 | if (!res.ok) throw new Error(mergeData.error || 'merge failed'); | ||
| 244 | } catch (e) { | ||
| 245 | statusMsg.textContent = 'merge error: ' + e.message; | ||
| 246 | statusMsg.className = 'error'; | ||
| 247 | busy = false; | ||
| 248 | mergeBtn.disabled = false; | ||
| 249 | return; | ||
| 250 | } | ||
| 251 | |||
| 252 | // 3. show the command | ||
| 253 | cmdSec.classList.remove('hidden'); | ||
| 254 | cmdPre.textContent = '$ ' + mergeData.command; | ||
| 255 | |||
| 256 | // 4. poll for completion | ||
| 257 | statusMsg.textContent = 'merging... this may take a while depending on slide count.'; | ||
| 258 | |||
| 259 | const poll = setInterval(async () => { | ||
| 260 | try { | ||
| 261 | const res = await fetch('/api/status/' + jobId); | ||
| 262 | const s = await res.json(); | ||
| 263 | |||
| 264 | if (s.log) { | ||
| 265 | logPre.classList.remove('hidden'); | ||
| 266 | logPre.textContent = s.log; | ||
| 267 | } | ||
| 268 | |||
| 269 | if (s.status === 'done') { | ||
| 270 | clearInterval(poll); | ||
| 271 | statusMsg.textContent = 'merge complete!'; | ||
| 272 | statusMsg.className = 'done'; | ||
| 273 | dlSec.classList.remove('hidden'); | ||
| 274 | dlLink.href = '/output/' + mergeData.output; | ||
| 275 | dlLink.download = mergeData.output; | ||
| 276 | busy = false; | ||
| 277 | mergeBtn.disabled = false; | ||
| 278 | } else if (s.status === 'error') { | ||
| 279 | clearInterval(poll); | ||
| 280 | statusMsg.textContent = 'merge failed — check the log below.'; | ||
| 281 | statusMsg.className = 'error'; | ||
| 282 | busy = false; | ||
| 283 | mergeBtn.disabled = false; | ||
| 284 | } | ||
| 285 | } catch (_) { | ||
| 286 | // network hiccup, keep polling | ||
| 287 | } | ||
| 288 | }, 2000); | ||
| 289 | }); | ||
| 290 | |||
| 291 | function rnd() { | ||
| 292 | return Math.random().toString(36).substring(2, 10); | ||
| 293 | } | ||
| 294 | })(); | ||
| 295 | </script> | 50 | </script> |
| 296 | </body> | 51 | </body> |
| 297 | </html> | 52 | </html> |
