aboutsummaryrefslogtreecommitdiff
path: root/demoware/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'demoware/index.html')
-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 @@
<title>slide merging demoware</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.min.css">
<style>
- .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; }
+ .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; }
</style>
</head>
<body>
@@ -88,210 +46,7 @@
</footer>
<script>
- (() => {
- 'use strict';
-
- // -- state --
- let files = []; // {id, name, file}
- let nextId = 1;
- let busy = false;
-
- // -- dom refs --
- const dropZone = document.getElementById('drop-zone');
- const fileInput = document.getElementById('file-input');
- const fileList = document.getElementById('file-list');
- const emptyMsg = document.getElementById('empty-msg');
- const mergeBtn = document.getElementById('merge-btn');
- const cmdSec = document.getElementById('cmd-section');
- const cmdPre = document.getElementById('command-display');
- const statusSec = document.getElementById('status-section');
- const statusMsg = document.getElementById('status-msg');
- const logPre = document.getElementById('log-output');
- const dlSec = document.getElementById('dl-section');
- const dlLink = document.getElementById('dl-link');
-
- // -- file input --
- fileInput.addEventListener('change', () => {
- addFiles(fileInput.files);
- fileInput.value = '';
- });
-
- // -- drag & drop --
- dropZone.addEventListener('dragover', e => {
- e.preventDefault();
- dropZone.classList.add('over');
- });
- dropZone.addEventListener('dragleave', () => dropZone.classList.remove('over'));
- dropZone.addEventListener('drop', e => {
- e.preventDefault();
- dropZone.classList.remove('over');
- addFiles(e.dataTransfer.files);
- });
- dropZone.addEventListener('click', () => fileInput.click());
-
- // -- list management --
-
- function addFiles(rawFiles) {
- for (const f of rawFiles) {
- if (!f.name.toLowerCase().endsWith('.pptx')) continue;
- files.push({ id: nextId++, name: f.name, file: f });
- }
- render();
- }
-
- function render() {
- fileList.innerHTML = '';
-
- if (files.length === 0) {
- emptyMsg.classList.remove('hidden');
- mergeBtn.disabled = true;
- return;
- }
-
- emptyMsg.classList.add('hidden');
- mergeBtn.disabled = busy;
-
- files.forEach((item, i) => {
- const row = document.createElement('div');
- row.className = 'file-item';
-
- const up = mkbtn('▲', i === 0, () => swap(i, i - 1));
- const dn = mkbtn('▼', i === files.length - 1, () => swap(i, i + 1));
- const rm = mkbtn('✕', false, () => { files.splice(i, 1); render(); });
-
- const span = document.createElement('span');
- span.className = 'fname';
- span.textContent = (i + 1) + '. ' + item.name;
-
- row.append(up, dn, rm, span);
- fileList.appendChild(row);
- });
- }
-
- function mkbtn(label, disabled, onclick) {
- const b = document.createElement('button');
- b.textContent = label;
- b.disabled = disabled;
- b.onclick = onclick;
- return b;
- }
-
- function swap(a, b) {
- [files[a], files[b]] = [files[b], files[a]];
- render();
- }
-
- // -- merge flow --
-
- mergeBtn.addEventListener('click', async () => {
- if (files.length === 0 || busy) return;
- busy = true;
- mergeBtn.disabled = true;
-
- // reset ui sections
- cmdSec.classList.add('hidden');
- statusSec.classList.add('hidden');
- dlSec.classList.add('hidden');
- logPre.textContent = '';
- logPre.classList.add('hidden');
-
- const jobId = Date.now() + '_' + rnd();
-
- statusSec.classList.remove('hidden');
-
- // 1. upload each file
- for (let i = 0; i < files.length; i++) {
- const item = files[i];
- statusMsg.textContent = 'uploading ' + (i + 1) + '/' + files.length + ': ' + item.name;
- statusMsg.className = 'running';
-
- const serverName = item.id + '_' + item.name;
- try {
- const res = await fetch('/api/upload', {
- method: 'POST',
- headers: {
- 'X-Job-Id': jobId,
- 'X-Filename': encodeURIComponent(serverName),
- },
- body: item.file,
- });
- if (!res.ok) {
- const err = await res.json().catch(() => ({ error: 'upload failed' }));
- throw new Error(err.error);
- }
- } catch (e) {
- statusMsg.textContent = 'upload error: ' + e.message;
- statusMsg.className = 'error';
- busy = false;
- mergeBtn.disabled = false;
- return;
- }
- }
-
- // 2. send merge request
- statusMsg.textContent = 'starting merge...';
- const serverFiles = files.map(f => f.id + '_' + f.name);
-
- let mergeData;
- try {
- const res = await fetch('/api/merge', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ job_id: jobId, files: serverFiles }),
- });
- mergeData = await res.json();
- if (!res.ok) throw new Error(mergeData.error || 'merge failed');
- } catch (e) {
- statusMsg.textContent = 'merge error: ' + e.message;
- statusMsg.className = 'error';
- busy = false;
- mergeBtn.disabled = false;
- return;
- }
-
- // 3. show the command
- cmdSec.classList.remove('hidden');
- cmdPre.textContent = '$ ' + mergeData.command;
-
- // 4. poll for completion
- statusMsg.textContent = 'merging... this may take a while depending on slide count.';
-
- const poll = setInterval(async () => {
- try {
- const res = await fetch('/api/status/' + jobId);
- const s = await res.json();
-
- if (s.log) {
- logPre.classList.remove('hidden');
- logPre.textContent = s.log;
- }
-
- if (s.status === 'done') {
- clearInterval(poll);
- statusMsg.textContent = 'merge complete!';
- statusMsg.className = 'done';
- dlSec.classList.remove('hidden');
- dlLink.href = '/output/' + mergeData.output;
- dlLink.download = mergeData.output;
- busy = false;
- mergeBtn.disabled = false;
- } else if (s.status === 'error') {
- clearInterval(poll);
- statusMsg.textContent = 'merge failed — check the log below.';
- statusMsg.className = 'error';
- busy = false;
- mergeBtn.disabled = false;
- }
- } catch (_) {
- // network hiccup, keep polling
- }
- }, 2000);
- });
-
- function rnd() {
- return Math.random().toString(36).substring(2, 10);
- }
- })();
+(()=>{"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)}))})();
</script>
</body>
</html>