aboutsummaryrefslogtreecommitdiff
path: root/demoware/index.html
blob: 5b5eba1348054606ff26b841872c5c92d9ebd39a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>slide merging demoware</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/kj-sh604/noir.css@latest/out/noir.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; }
    </style>
</head>
<body>
    <h1>an attempt at a .pptx merging tool</h1>
    <p>merge multiple <code>.pptx</code> files into a <em>single</em> <code>.pptx</code> file.</p>
    <hr>

    <h2>1. add files</h2>
    <div id="drop-zone">
        drop <code>.pptx</code> files here
    </div>
    <input type="file" id="file-input" multiple accept=".pptx" style="margin-top:0.5rem;">

    <h2>2. merge order</h2>
    <p id="empty-msg"><em>no files added yet.</em></p>
    <div id="file-list"></div>

    <h2>3. merge</h2>
    <button id="merge-btn" disabled>merge presentations</button>

    <div id="cmd-section" class="hidden">
        <h3><code>kjandoc</code> command being used:</h3>
        <pre id="command-display"></pre>
    </div>

    <div id="status-section" class="hidden">
        <p id="status-msg"></p>
        <pre id="log-output" class="hidden"></pre>
    </div>

    <div id="dl-section" class="hidden">
        <a id="dl-link"><button>⬇ download merged presentation</button></a>
    </div>

    <footer>
        <small>powered with <a href="https://github.com/kj-sh604/kjandoc" target="_blank" rel="noopener">kjandoc</a> by <a href="https://github.com/kj-sh604" target="_blank" rel="noopener">kj_sh604</a></small>
    </footer>

    <script>
(()=>{"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>