summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkj_sh6042026-02-14 00:16:21 -0500
committerkj_sh6042026-02-14 00:16:21 -0500
commit5d527cb99cd733f02db25ac4666e817699a8a5a0 (patch)
treef03af296a449e2f3aee372c0d20edd979e0e64a5
parentd44a2f72e988d423a7ad6531f6779aff82edc848 (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.html249
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>