File size: 5,227 Bytes
8ac3a87
 
 
31c4a0e
c81c490
8ac3a87
 
 
898cc4c
66aed24
 
 
8ac3a87
 
 
 
 
66aed24
8ac3a87
 
 
 
 
08e6637
66aed24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd3060e
08e6637
66aed24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd3060e
66aed24
 
 
 
 
 
8ac3a87
fd3060e
66aed24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd3060e
66aed24
 
 
 
 
 
 
 
 
 
 
fd3060e
66aed24
 
 
fd3060e
66aed24
 
 
 
 
 
8ac3a87
08e6637
66aed24
08e6637
 
8ac3a87
 
66aed24
31c4a0e
 
66aed24
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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>
"""

@app.get("/", response_class=HTMLResponse)
async def root():
    return HTML

if __name__ == "__main__":
    uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))