Spaces:
Paused
Paused
| from fastapi import FastAPI | |
| from fastapi.responses import HTMLResponse | |
| from fastapi.staticfiles import StaticFiles | |
| import pathlib, os, uvicorn | |
| BASE = pathlib.Path(__file__).parent | |
| app = FastAPI() | |
| app.mount("/static", StaticFiles(directory=BASE), name="static") | |
| HTML = """ | |
| <!doctype html><html lang="ko"><head> | |
| <meta charset="utf-8"><title>FlipBook Space</title> | |
| <link rel="stylesheet" href="/static/flipbook.css"> | |
| <script src="/static/three.js"></script> | |
| <script src="/static/iscroll.js"></script> | |
| <script src="/static/mark.js"></script> | |
| <script src="/static/mod3d.js"></script> | |
| <script src="/static/pdf.js"></script> | |
| <script src="/static/flipbook.js"></script> | |
| <script src="/static/flipbook.book3.js"></script> | |
| <script src="/static/flipbook.scroll.js"></script> | |
| <script src="/static/flipbook.swipe.js"></script> | |
| <script src="/static/flipbook.webgl.js"></script> | |
| <style> | |
| body{margin:0;background:#f0f0f0;font-family:sans-serif} | |
| header{max-width:960px;margin:0 auto;padding:18px 20px;display:flex;align-items:center} | |
| #homeBtn{display:none;width:38px;height:38px;border:none;border-radius:50%;cursor:pointer; | |
| background:#0077c2;color:#fff;font-size:20px;margin-right:12px} | |
| #homeBtn:hover{background:#005999} | |
| h2{margin:0;font-size:1.5rem;font-weight:600} | |
| #home,#viewerPage{max-width:960px;margin:0 auto;padding:0 20px 40px} | |
| .grid{display:grid;grid-template-columns:repeat(auto-fill,180px);gap:16px;margin-top:24px} | |
| .card{background:#fff;border:1px solid #ccc;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px rgba(0,0,0,.12)} | |
| .card img{width:100%;height:140px;object-fit:cover} | |
| .card p{text-align:center;margin:6px 0} | |
| button.upload{all:unset;cursor:pointer;border:1px solid #bbb;padding:8px 14px;border-radius:6px;background:#fff;margin:0 8px} | |
| #viewer{width:100%;max-width:1200px;height:80vh;margin:24px auto;background:#fff;border:1px solid #ccc} | |
| </style></head><body> | |
| <header> | |
| <button id="homeBtn" title="νμΌλ‘">β</button> | |
| <h2>My FlipBook Projects</h2> | |
| </header> | |
| <section id="home"> | |
| <div> | |
| <label class="upload">π· μ΄λ―Έμ§ <input id="imgInput" type="file" accept="image/*" multiple hidden></label> | |
| <label class="upload">π PDF <input id="pdfInput" type="file" accept="application/pdf" hidden></label> | |
| </div> | |
| <div class="grid" id="grid"></div> | |
| </section> | |
| <section id="viewerPage" style="display:none"> | |
| <div id="viewer"></div> | |
| </section> | |
| <script> | |
| let projects=[], fb=null; | |
| const grid=$id('grid'), viewer=$id('viewer'); | |
| pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js'; | |
| /* π μ€λμ€ unlock β λ΄μ₯ Audio μ κ°μ MP3 κ²½λ‘ μ¬μ© */ | |
| ['click','touchstart'].forEach(evt=>{ | |
| document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3') | |
| .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});}, | |
| {once:true,capture:true}); | |
| }); | |
| /* ββ μ νΈ ββ */ | |
| function $id(id){return document.getElementById(id)} | |
| function addCard(i,thumb){ | |
| const d=document.createElement('div');d.className='card';d.onclick=()=>open(i); | |
| d.innerHTML=`<img src="${thumb}"><p>νλ‘μ νΈ ${i+1}</p>`;grid.appendChild(d); | |
| } | |
| /* ββ μ΄λ―Έμ§ μ λ‘λ ββ */ | |
| $id('imgInput').onchange=e=>{ | |
| const files=[...e.target.files]; if(!files.length) return; | |
| const pages=[],tot=files.length;let done=0; | |
| files.forEach((f,i)=>{const r=new FileReader();r.onload=x=>{pages[i]={src:x.target.result,thumb:x.target.result}; | |
| if(++done===tot) save(pages);};r.readAsDataURL(f);}); | |
| }; | |
| /* ββ PDF μ λ‘λ ββ */ | |
| $id('pdfInput').onchange=e=>{ | |
| const file=e.target.files[0]; if(!file) return; | |
| const fr=new FileReader(); | |
| fr.onload=v=>{ | |
| pdfjsLib.getDocument({data:v.target.result}).promise.then(async pdf=>{ | |
| const pages=[]; | |
| for(let p=1;p<=pdf.numPages;p++){ | |
| const pg=await pdf.getPage(p), vp=pg.getViewport({scale:1}); | |
| const c=document.createElement('canvas');c.width=vp.width;c.height=vp.height; | |
| await pg.render({canvasContext:c.getContext('2d'),viewport:vp}).promise; | |
| pages.push({src:c.toDataURL(),thumb:c.toDataURL()}); | |
| } | |
| save(pages); | |
| }); | |
| };fr.readAsArrayBuffer(file); | |
| }; | |
| /* ββ νλ‘μ νΈ μ μ₯ ββ */ | |
| function save(pages){const id=projects.push(pages)-1;addCard(id,pages[0].thumb);} | |
| /* ββ μΉ΄λ β FlipBook ββ */ | |
| function open(i){ | |
| toggle(false); | |
| const pages=projects[i]; | |
| if(fb){fb.destroy();viewer.innerHTML='';} | |
| fb=new FlipBook(viewer,{ | |
| pages,viewMode:'webgl',autoSize:true,flipDuration:800,backgroundColor:'#fff', | |
| /* π λ΄μ₯ μ¬μ΄λ */ | |
| sound:true, | |
| assets:{flipMp3:'static/turnPage2.mp3',hardFlipMp3:'static/turnPage2.mp3'}, | |
| controlsProps:{enableFullscreen:true,thumbnails:true}}); | |
| } | |
| /* ββ λ€λΉκ²μ΄μ ββ */ | |
| $id('homeBtn').onclick=()=>toggle(true); | |
| function toggle(showHome){ | |
| $id('home').style.display=showHome?'block':'none'; | |
| $id('viewerPage').style.display=showHome?'none':'block'; | |
| $id('homeBtn').style.display=showHome?'none':'inline-block'; | |
| } | |
| </script> | |
| </body></html> | |
| """ | |
| async def root(): | |
| return HTML | |
| if __name__ == "__main__": | |
| uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860))) | |