ginipick commited on
Commit
d534122
ยท
verified ยท
1 Parent(s): 8b4f960

Delete app-BACKUP-LAST.py

Browse files
Files changed (1) hide show
  1. app-BACKUP-LAST.py +0 -1645
app-BACKUP-LAST.py DELETED
@@ -1,1645 +0,0 @@
1
- from fastapi import FastAPI, BackgroundTasks
2
- from fastapi.responses import HTMLResponse, JSONResponse, Response
3
- from fastapi.staticfiles import StaticFiles
4
- import pathlib, os, uvicorn, base64, json
5
- from typing import Dict, List, Any
6
- import asyncio
7
- import logging
8
- import threading
9
- import concurrent.futures
10
-
11
- # ๋กœ๊น… ์„ค์ •
12
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
13
- logger = logging.getLogger(__name__)
14
-
15
- BASE = pathlib.Path(__file__).parent
16
- app = FastAPI()
17
- app.mount("/static", StaticFiles(directory=BASE), name="static")
18
-
19
- # PDF ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
20
- PDF_DIR = BASE / "pdf"
21
- if not PDF_DIR.exists():
22
- PDF_DIR.mkdir(parents=True)
23
-
24
- # ์บ์‹œ ๋””๋ ‰ํ† ๋ฆฌ ์„ค์ •
25
- CACHE_DIR = BASE / "cache"
26
- if not CACHE_DIR.exists():
27
- CACHE_DIR.mkdir(parents=True)
28
-
29
- # ์ „์—ญ ์บ์‹œ ๊ฐ์ฒด
30
- pdf_cache: Dict[str, Dict[str, Any]] = {}
31
- # ์บ์‹ฑ ๋ฝ
32
- cache_locks = {}
33
-
34
- # PDF ํŒŒ์ผ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
35
- def get_pdf_files():
36
- pdf_files = []
37
- if PDF_DIR.exists():
38
- pdf_files = [f for f in PDF_DIR.glob("*.pdf")]
39
- return pdf_files
40
-
41
- # PDF ์ธ๋„ค์ผ ์ƒ์„ฑ ๋ฐ ํ”„๋กœ์ ํŠธ ๋ฐ์ดํ„ฐ ์ค€๋น„
42
- def generate_pdf_projects():
43
- projects_data = []
44
- pdf_files = get_pdf_files()
45
-
46
- for pdf_file in pdf_files:
47
- projects_data.append({
48
- "path": str(pdf_file),
49
- "name": pdf_file.stem,
50
- "cached": pdf_file.stem in pdf_cache and pdf_cache[pdf_file.stem].get("status") == "completed"
51
- })
52
-
53
- return projects_data
54
-
55
- # ์บ์‹œ ํŒŒ์ผ ๊ฒฝ๋กœ ์ƒ์„ฑ
56
- def get_cache_path(pdf_name: str):
57
- return CACHE_DIR / f"{pdf_name}_cache.json"
58
-
59
- # ์ตœ์ ํ™”๋œ PDF ํŽ˜์ด์ง€ ์บ์‹ฑ ํ•จ์ˆ˜
60
- async def cache_pdf(pdf_path: str):
61
- try:
62
- import fitz # PyMuPDF
63
-
64
- pdf_file = pathlib.Path(pdf_path)
65
- pdf_name = pdf_file.stem
66
-
67
- # ๋ฝ ์ƒ์„ฑ - ๋™์ผํ•œ PDF์— ๋Œ€ํ•ด ๋™์‹œ ์บ์‹ฑ ๋ฐฉ์ง€
68
- if pdf_name not in cache_locks:
69
- cache_locks[pdf_name] = threading.Lock()
70
-
71
- # ์ด๋ฏธ ์บ์‹ฑ ์ค‘์ด๊ฑฐ๋‚˜ ์บ์‹ฑ ์™„๋ฃŒ๋œ PDF๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ
72
- if pdf_name in pdf_cache and pdf_cache[pdf_name].get("status") in ["processing", "completed"]:
73
- logger.info(f"PDF {pdf_name} ์ด๋ฏธ ์บ์‹ฑ ์™„๋ฃŒ ๋˜๋Š” ์ง„ํ–‰ ์ค‘")
74
- return
75
-
76
- with cache_locks[pdf_name]:
77
- # ์ด์ค‘ ์ฒดํฌ - ๋ฝ ํš๋“ ํ›„ ๋‹ค์‹œ ํ™•์ธ
78
- if pdf_name in pdf_cache and pdf_cache[pdf_name].get("status") in ["processing", "completed"]:
79
- return
80
-
81
- # ์บ์‹œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
82
- pdf_cache[pdf_name] = {"status": "processing", "progress": 0, "pages": []}
83
-
84
- # ์บ์‹œ ํŒŒ์ผ์ด ์ด๋ฏธ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ
85
- cache_path = get_cache_path(pdf_name)
86
- if cache_path.exists():
87
- try:
88
- with open(cache_path, "r") as cache_file:
89
- cached_data = json.load(cache_file)
90
- if cached_data.get("status") == "completed" and cached_data.get("pages"):
91
- pdf_cache[pdf_name] = cached_data
92
- pdf_cache[pdf_name]["status"] = "completed"
93
- logger.info(f"์บ์‹œ ํŒŒ์ผ์—์„œ {pdf_name} ๋กœ๋“œ ์™„๋ฃŒ")
94
- return
95
- except Exception as e:
96
- logger.error(f"์บ์‹œ ํŒŒ์ผ ๋กœ๋“œ ์‹คํŒจ: {e}")
97
-
98
- # PDF ํŒŒ์ผ ์—ด๊ธฐ
99
- doc = fitz.open(pdf_path)
100
- total_pages = doc.page_count
101
-
102
- # ๋ฏธ๋ฆฌ ์ธ๋„ค์ผ๋งŒ ๋จผ์ € ์ƒ์„ฑ (๋น ๋ฅธ UI ๋กœ๋”ฉ์šฉ)
103
- if total_pages > 0:
104
- # ์ฒซ ํŽ˜์ด์ง€ ์ธ๋„ค์ผ ์ƒ์„ฑ
105
- page = doc[0]
106
- pix_thumb = page.get_pixmap(matrix=fitz.Matrix(0.2, 0.2)) # ๋” ์ž‘์€ ์ธ๋„ค์ผ
107
- thumb_data = pix_thumb.tobytes("png")
108
- b64_thumb = base64.b64encode(thumb_data).decode('utf-8')
109
- thumb_src = f"data:image/png;base64,{b64_thumb}"
110
-
111
- # ์ธ๋„ค์ผ ํŽ˜์ด์ง€๋งŒ ๋จผ์ € ์บ์‹œ
112
- pdf_cache[pdf_name]["pages"] = [{"thumb": thumb_src, "src": ""}]
113
- pdf_cache[pdf_name]["progress"] = 1
114
- pdf_cache[pdf_name]["total_pages"] = total_pages
115
-
116
- # ์ด๋ฏธ์ง€ ํ•ด์ƒ๋„ ๋ฐ ์••์ถ• ํ’ˆ์งˆ ์„ค์ • (์„ฑ๋Šฅ ์ตœ์ ํ™”)
117
- scale_factor = 1.0 # ๊ธฐ๋ณธ ํ•ด์ƒ๋„ (๋‚ฎ์ถœ์ˆ˜๋ก ๋กœ๋”ฉ ๋น ๋ฆ„)
118
- jpeg_quality = 80 # JPEG ํ’ˆ์งˆ (๋‚ฎ์ถœ์ˆ˜๋ก ์šฉ๋Ÿ‰ ์ž‘์•„์ง)
119
-
120
- # ํŽ˜์ด์ง€ ์ฒ˜๋ฆฌ ์ž‘์—…์ž ํ•จ์ˆ˜ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ์šฉ)
121
- def process_page(page_num):
122
- try:
123
- page = doc[page_num]
124
-
125
- # ์ด๋ฏธ์ง€๋กœ ๋ณ€ํ™˜ ์‹œ ๋งคํŠธ๋ฆญ์Šค ์Šค์ผ€์ผ๋ง ์ ์šฉ (์„ฑ๋Šฅ ์ตœ์ ํ™”)
126
- pix = page.get_pixmap(matrix=fitz.Matrix(scale_factor, scale_factor))
127
-
128
- # JPEG ํ˜•์‹์œผ๋กœ ์ธ์ฝ”๋”ฉ (PNG๋ณด๋‹ค ํฌ๊ธฐ ์ž‘์Œ)
129
- img_data = pix.tobytes("jpeg", jpeg_quality)
130
- b64_img = base64.b64encode(img_data).decode('utf-8')
131
- img_src = f"data:image/jpeg;base64,{b64_img}"
132
-
133
- # ์ธ๋„ค์ผ (์ฒซ ํŽ˜์ด์ง€๊ฐ€ ์•„๋‹ˆ๋ฉด ๋นˆ ๋ฌธ์ž์—ด)
134
- thumb_src = "" if page_num > 0 else pdf_cache[pdf_name]["pages"][0]["thumb"]
135
-
136
- return {
137
- "page_num": page_num,
138
- "src": img_src,
139
- "thumb": thumb_src
140
- }
141
- except Exception as e:
142
- logger.error(f"ํŽ˜์ด์ง€ {page_num} ์ฒ˜๋ฆฌ ์˜ค๋ฅ˜: {e}")
143
- return {
144
- "page_num": page_num,
145
- "src": "",
146
- "thumb": "",
147
- "error": str(e)
148
- }
149
-
150
- # ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ๋ชจ๋“  ํŽ˜์ด์ง€ ์ฒ˜๋ฆฌ
151
- pages = [None] * total_pages
152
- processed_count = 0
153
-
154
- # ํŽ˜์ด์ง€ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ (๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ)
155
- batch_size = 5 # ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•  ํŽ˜์ด์ง€ ์ˆ˜
156
-
157
- for batch_start in range(0, total_pages, batch_size):
158
- batch_end = min(batch_start + batch_size, total_pages)
159
- current_batch = list(range(batch_start, batch_end))
160
-
161
- # ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ๋ฐฐ์น˜ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง
162
- with concurrent.futures.ThreadPoolExecutor(max_workers=min(5, batch_size)) as executor:
163
- batch_results = list(executor.map(process_page, current_batch))
164
-
165
- # ๊ฒฐ๊ณผ ์ €์žฅ
166
- for result in batch_results:
167
- page_num = result["page_num"]
168
- pages[page_num] = {
169
- "src": result["src"],
170
- "thumb": result["thumb"]
171
- }
172
-
173
- processed_count += 1
174
- progress = round(processed_count / total_pages * 100)
175
- pdf_cache[pdf_name]["progress"] = progress
176
-
177
- # ์ค‘๊ฐ„ ์ €์žฅ
178
- pdf_cache[pdf_name]["pages"] = pages
179
- try:
180
- with open(cache_path, "w") as cache_file:
181
- json.dump({
182
- "status": "processing",
183
- "progress": pdf_cache[pdf_name]["progress"],
184
- "pages": pdf_cache[pdf_name]["pages"],
185
- "total_pages": total_pages
186
- }, cache_file)
187
- except Exception as e:
188
- logger.error(f"์ค‘๊ฐ„ ์บ์‹œ ์ €์žฅ ์‹คํŒจ: {e}")
189
-
190
- # ์บ์‹ฑ ์™„๋ฃŒ
191
- pdf_cache[pdf_name] = {
192
- "status": "completed",
193
- "progress": 100,
194
- "pages": pages,
195
- "total_pages": total_pages
196
- }
197
-
198
- # ์ตœ์ข… ์บ์‹œ ํŒŒ์ผ ์ €์žฅ
199
- try:
200
- with open(cache_path, "w") as cache_file:
201
- json.dump(pdf_cache[pdf_name], cache_file)
202
- logger.info(f"PDF {pdf_name} ์บ์‹ฑ ์™„๋ฃŒ, {total_pages}ํŽ˜์ด์ง€")
203
- except Exception as e:
204
- logger.error(f"์ตœ์ข… ์บ์‹œ ์ €์žฅ ์‹คํŒจ: {e}")
205
-
206
- except Exception as e:
207
- import traceback
208
- logger.error(f"PDF ์บ์‹ฑ ์˜ค๋ฅ˜: {str(e)}\n{traceback.format_exc()}")
209
- if pdf_name in pdf_cache:
210
- pdf_cache[pdf_name]["status"] = "error"
211
- pdf_cache[pdf_name]["error"] = str(e)
212
-
213
- # ์‹œ์ž‘ ์‹œ ๋ชจ๋“  PDF ํŒŒ์ผ ์บ์‹ฑ
214
- async def init_cache_all_pdfs():
215
- logger.info("PDF ์บ์‹ฑ ์ž‘์—… ์‹œ์ž‘")
216
- pdf_files = get_pdf_files()
217
-
218
- # ์ด๋ฏธ ์บ์‹œ๋œ PDF ํŒŒ์ผ ๋กœ๋“œ (๋น ๋ฅธ ์‹œ์ž‘์„ ์œ„ํ•ด ๋จผ์ € ์ˆ˜ํ–‰)
219
- for cache_file in CACHE_DIR.glob("*_cache.json"):
220
- try:
221
- pdf_name = cache_file.stem.replace("_cache", "")
222
- with open(cache_file, "r") as f:
223
- cached_data = json.load(f)
224
- if cached_data.get("status") == "completed" and cached_data.get("pages"):
225
- pdf_cache[pdf_name] = cached_data
226
- pdf_cache[pdf_name]["status"] = "completed"
227
- logger.info(f"๊ธฐ์กด ์บ์‹œ ๋กœ๋“œ: {pdf_name}")
228
- except Exception as e:
229
- logger.error(f"์บ์‹œ ํŒŒ์ผ ๋กœ๋“œ ์˜ค๋ฅ˜: {str(e)}")
230
-
231
- # ์บ์‹ฑ๋˜์ง€ ์•Š์€ PDF ํŒŒ์ผ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
232
- await asyncio.gather(*[asyncio.create_task(cache_pdf(str(pdf_file)))
233
- for pdf_file in pdf_files
234
- if pdf_file.stem not in pdf_cache
235
- or pdf_cache[pdf_file.stem].get("status") != "completed"])
236
-
237
- # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—… ์‹œ์ž‘ ํ•จ์ˆ˜
238
- @app.on_event("startup")
239
- async def startup_event():
240
- # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํƒœ์Šคํฌ๋กœ ์บ์‹ฑ ์‹คํ–‰
241
- asyncio.create_task(init_cache_all_pdfs())
242
-
243
- # API ์—”๋“œํฌ์ธํŠธ: PDF ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก
244
- @app.get("/api/pdf-projects")
245
- async def get_pdf_projects_api():
246
- return generate_pdf_projects()
247
-
248
- # API ์—”๋“œํฌ์ธํŠธ: PDF ์ธ๋„ค์ผ ์ œ๊ณต (์ตœ์ ํ™”)
249
- @app.get("/api/pdf-thumbnail")
250
- async def get_pdf_thumbnail(path: str):
251
- try:
252
- pdf_file = pathlib.Path(path)
253
- pdf_name = pdf_file.stem
254
-
255
- # ์บ์‹œ์—์„œ ์ธ๋„ค์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
256
- if pdf_name in pdf_cache and pdf_cache[pdf_name].get("pages"):
257
- if pdf_cache[pdf_name]["pages"][0].get("thumb"):
258
- return {"thumbnail": pdf_cache[pdf_name]["pages"][0]["thumb"]}
259
-
260
- # ์บ์‹œ์— ์—†์œผ๋ฉด ์ƒ์„ฑ (๋” ์ž‘๊ณ  ๋น ๋ฅธ ์ธ๋„ค์ผ)
261
- import fitz
262
- doc = fitz.open(path)
263
- if doc.page_count > 0:
264
- page = doc[0]
265
- pix = page.get_pixmap(matrix=fitz.Matrix(0.2, 0.2)) # ๋” ์ž‘์€ ์ธ๋„ค์ผ
266
- img_data = pix.tobytes("jpeg", 70) # JPEG ์••์ถ• ์‚ฌ์šฉ
267
- b64_img = base64.b64encode(img_data).decode('utf-8')
268
-
269
- # ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์บ์‹ฑ ์‹œ์ž‘
270
- asyncio.create_task(cache_pdf(path))
271
-
272
- return {"thumbnail": f"data:image/jpeg;base64,{b64_img}"}
273
-
274
- return {"thumbnail": None}
275
- except Exception as e:
276
- logger.error(f"์ธ๋„ค์ผ ์ƒ์„ฑ ์˜ค๋ฅ˜: {str(e)}")
277
- return {"error": str(e), "thumbnail": None}
278
-
279
- # API ์—”๋“œํฌ์ธํŠธ: ์บ์‹œ ์ƒํƒœ ํ™•์ธ
280
- @app.get("/api/cache-status")
281
- async def get_cache_status(path: str = None):
282
- if path:
283
- pdf_file = pathlib.Path(path)
284
- pdf_name = pdf_file.stem
285
- if pdf_name in pdf_cache:
286
- return pdf_cache[pdf_name]
287
- return {"status": "not_cached"}
288
- else:
289
- return {name: {"status": info["status"], "progress": info.get("progress", 0)}
290
- for name, info in pdf_cache.items()}
291
-
292
- # API ์—”๋“œํฌ์ธํŠธ: ์บ์‹œ๋œ PDF ์ฝ˜ํ…์ธ  ์ œ๊ณต (์ ์ง„์  ๋กœ๋”ฉ ์ง€์›)
293
- @app.get("/api/cached-pdf")
294
- async def get_cached_pdf(path: str, background_tasks: BackgroundTasks):
295
- try:
296
- pdf_file = pathlib.Path(path)
297
- pdf_name = pdf_file.stem
298
-
299
- # ์บ์‹œ ํ™•์ธ
300
- if pdf_name in pdf_cache:
301
- status = pdf_cache[pdf_name].get("status", "")
302
-
303
- # ์™„๋ฃŒ๋œ ๊ฒฝ์šฐ ์ „์ฒด ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
304
- if status == "completed":
305
- return pdf_cache[pdf_name]
306
-
307
- # ์ฒ˜๋ฆฌ ์ค‘์ธ ๊ฒฝ์šฐ ํ˜„์žฌ๊นŒ์ง€์˜ ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ ํฌํ•จ (์ ์ง„์  ๋กœ๋”ฉ)
308
- elif status == "processing":
309
- progress = pdf_cache[pdf_name].get("progress", 0)
310
- pages = pdf_cache[pdf_name].get("pages", [])
311
- total_pages = pdf_cache[pdf_name].get("total_pages", 0)
312
-
313
- # ์ผ๋ถ€๋งŒ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ์—๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŽ˜์ด์ง€ ์ œ๊ณต
314
- return {
315
- "status": "processing",
316
- "progress": progress,
317
- "pages": pages,
318
- "total_pages": total_pages,
319
- "available_pages": len([p for p in pages if p and p.get("src")])
320
- }
321
-
322
- # ์บ์‹œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์บ์‹ฑ ์‹œ์ž‘
323
- background_tasks.add_task(cache_pdf, path)
324
- return {"status": "started", "progress": 0}
325
-
326
- except Exception as e:
327
- logger.error(f"์บ์‹œ๋œ PDF ์ œ๊ณต ์˜ค๋ฅ˜: {str(e)}")
328
- return {"error": str(e), "status": "error"}
329
-
330
- # API ์—”๋“œํฌ์ธํŠธ: PDF ์›๋ณธ ์ฝ˜ํ…์ธ  ์ œ๊ณต(์บ์‹œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ)
331
- @app.get("/api/pdf-content")
332
- async def get_pdf_content(path: str, background_tasks: BackgroundTasks):
333
- try:
334
- # ์บ์‹ฑ ์ƒํƒœ ํ™•์ธ
335
- pdf_file = pathlib.Path(path)
336
- if not pdf_file.exists():
337
- return JSONResponse(content={"error": f"ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: {path}"}, status_code=404)
338
-
339
- pdf_name = pdf_file.stem
340
-
341
- # ์บ์‹œ๋œ ๊ฒฝ์šฐ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
342
- if pdf_name in pdf_cache and (pdf_cache[pdf_name].get("status") == "completed"
343
- or (pdf_cache[pdf_name].get("status") == "processing"
344
- and pdf_cache[pdf_name].get("progress", 0) > 10)):
345
- return JSONResponse(content={"redirect": f"/api/cached-pdf?path={path}"})
346
-
347
- # ํŒŒ์ผ ์ฝ๊ธฐ
348
- with open(path, "rb") as pdf_file:
349
- content = pdf_file.read()
350
-
351
- # ํŒŒ์ผ๋ช… ์ฒ˜๋ฆฌ
352
- import urllib.parse
353
- filename = pdf_file.name
354
- encoded_filename = urllib.parse.quote(filename)
355
-
356
- # ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์บ์‹ฑ ์‹œ์ž‘
357
- background_tasks.add_task(cache_pdf, path)
358
-
359
- # ์‘๋‹ต ํ—ค๋” ์„ค์ •
360
- headers = {
361
- "Content-Type": "application/pdf",
362
- "Content-Disposition": f"inline; filename=\"{encoded_filename}\"; filename*=UTF-8''{encoded_filename}"
363
- }
364
-
365
- return Response(content=content, media_type="application/pdf", headers=headers)
366
- except Exception as e:
367
- import traceback
368
- error_details = traceback.format_exc()
369
- logger.error(f"PDF ์ฝ˜ํ…์ธ  ๋กœ๋“œ ์˜ค๋ฅ˜: {str(e)}\n{error_details}")
370
- return JSONResponse(content={"error": str(e)}, status_code=500)
371
-
372
- # HTML ํŒŒ์ผ ์ฝ๊ธฐ ํ•จ์ˆ˜
373
- def get_html_content():
374
- html_path = BASE / "flipbook_template.html"
375
- if html_path.exists():
376
- with open(html_path, "r", encoding="utf-8") as f:
377
- return f.read()
378
- return HTML # ๊ธฐ๋ณธ HTML ์‚ฌ์šฉ
379
-
380
- # HTML ๋ฌธ์ž์—ด (UI ์ˆ˜์ • ๋ฒ„์ „)
381
- HTML = """
382
- <!doctype html>
383
- <html lang="ko">
384
- <head>
385
- <meta charset="utf-8">
386
- <title>FlipBook Space</title>
387
- <link rel="stylesheet" href="/static/flipbook.css">
388
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
389
- <script src="/static/three.js"></script>
390
- <script src="/static/iscroll.js"></script>
391
- <script src="/static/mark.js"></script>
392
- <script src="/static/mod3d.js"></script>
393
- <script src="/static/pdf.js"></script>
394
- <script src="/static/flipbook.js"></script>
395
- <script src="/static/flipbook.book3.js"></script>
396
- <script src="/static/flipbook.scroll.js"></script>
397
- <script src="/static/flipbook.swipe.js"></script>
398
- <script src="/static/flipbook.webgl.js"></script>
399
- <style>
400
- /* ์ „์ฒด ์‚ฌ์ดํŠธ ํŒŒ์Šคํ…”ํ†ค ํ…Œ๋งˆ */
401
- :root {
402
- --primary-color: #a5d8ff; /* ํŒŒ์Šคํ…” ๋ธ”๋ฃจ */
403
- --secondary-color: #ffd6e0; /* ํŒŒ์Šคํ…” ํ•‘ํฌ */
404
- --tertiary-color: #c3fae8; /* ํŒŒ์Šคํ…” ๋ฏผํŠธ */
405
- --accent-color: #d0bfff; /* ํŒŒ์Šคํ…” ํผํ”Œ */
406
- --bg-color: #f8f9fa; /* ๋ฐ์€ ๋ฐฐ๊ฒฝ */
407
- --text-color: #495057; /* ๋ถ€๋“œ๋Ÿฌ์šด ์–ด๋‘์šด ์ƒ‰ */
408
- --card-bg: #ffffff; /* ์นด๋“œ ๋ฐฐ๊ฒฝ์ƒ‰ */
409
- --shadow-sm: 0 2px 8px rgba(0,0,0,0.05);
410
- --shadow-md: 0 4px 12px rgba(0,0,0,0.08);
411
- --shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
412
- --radius-sm: 8px;
413
- --radius-md: 12px;
414
- --radius-lg: 16px;
415
- --transition: all 0.3s ease;
416
- }
417
-
418
-
419
- body {
420
- margin: 0;
421
- background: var(--bg-color);
422
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
423
- color: var(--text-color);
424
- /* ์ƒˆ๋กœ์šด ํผํ”Œ ๊ณ„ํ†ต ๊ณ ๊ธ‰์Šค๋Ÿฌ์šด ๊ทธ๋ผ๋””์—์ด์…˜ ๋ฐฐ๊ฒฝ */
425
- background-image: linear-gradient(135deg, #2a0845 0%, #6441a5 50%, #c9a8ff 100%);
426
- background-attachment: fixed;
427
- }
428
-
429
- /* ๋ทฐ์–ด ๋ชจ๋“œ์ผ ๋•Œ ๋ฐฐ๊ฒฝ ๋ณ€๊ฒฝ */
430
- .viewer-mode {
431
- background-image: linear-gradient(135deg, #30154e 0%, #6b47ad 50%, #d5b8ff 100%) !important;
432
- }
433
-
434
-
435
- /* ํ—ค๋” ์ œ๋ชฉ ์ œ๊ฑฐ ๋ฐ Home ๋ฒ„ํŠผ ๋ ˆ์ด์–ด ์ฒ˜๋ฆฌ */
436
- .floating-home {
437
- position: fixed;
438
- top: 20px;
439
- left: 20px;
440
- width: 60px;
441
- height: 60px;
442
- border-radius: 50%;
443
- background: rgba(255, 255, 255, 0.9);
444
- backdrop-filter: blur(10px);
445
- box-shadow: var(--shadow-md);
446
- z-index: 9999;
447
- display: flex;
448
- justify-content: center;
449
- align-items: center;
450
- cursor: pointer;
451
- transition: var(--transition);
452
- overflow: hidden;
453
- }
454
-
455
- .floating-home:hover {
456
- transform: scale(1.05);
457
- box-shadow: var(--shadow-lg);
458
- }
459
-
460
- .floating-home .icon {
461
- display: flex;
462
- justify-content: center;
463
- align-items: center;
464
- width: 100%;
465
- height: 100%;
466
- font-size: 22px;
467
- color: var(--primary-color);
468
- transition: var(--transition);
469
- }
470
-
471
- .floating-home:hover .icon {
472
- color: #8bc5f8;
473
- }
474
-
475
- .floating-home .title {
476
- position: absolute;
477
- left: 70px;
478
- background: rgba(255, 255, 255, 0.95);
479
- padding: 8px 20px;
480
- border-radius: 20px;
481
- box-shadow: var(--shadow-sm);
482
- font-weight: 600;
483
- font-size: 14px;
484
- white-space: nowrap;
485
- pointer-events: none;
486
- opacity: 0;
487
- transform: translateX(-10px);
488
- transition: all 0.3s ease;
489
- }
490
-
491
- .floating-home:hover .title {
492
- opacity: 1;
493
- transform: translateX(0);
494
- }
495
-
496
- #home, #viewerPage {
497
- padding-top: 100px;
498
- max-width: 1200px;
499
- margin: 0 auto;
500
- padding-bottom: 60px;
501
- padding-left: 30px;
502
- padding-right: 30px;
503
- position: relative;
504
- }
505
-
506
- /* ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
507
- .upload-container {
508
- display: flex;
509
- margin-bottom: 30px;
510
- justify-content: center;
511
- }
512
-
513
- button.upload {
514
- all: unset;
515
- cursor: pointer;
516
- padding: 12px 20px;
517
- border-radius: var(--radius-md);
518
- background: white;
519
- margin: 0 10px;
520
- font-weight: 500;
521
- display: flex;
522
- align-items: center;
523
- box-shadow: var(--shadow-sm);
524
- transition: var(--transition);
525
- position: relative;
526
- overflow: hidden;
527
- }
528
-
529
- button.upload::before {
530
- content: '';
531
- position: absolute;
532
- top: 0;
533
- left: 0;
534
- width: 100%;
535
- height: 100%;
536
- background: linear-gradient(120deg, var(--primary-color) 0%, var(--secondary-color) 100%);
537
- opacity: 0.08;
538
- z-index: -1;
539
- }
540
-
541
- button.upload:hover {
542
- transform: translateY(-3px);
543
- box-shadow: var(--shadow-md);
544
- }
545
-
546
- button.upload:hover::before {
547
- opacity: 0.15;
548
- }
549
-
550
- button.upload i {
551
- margin-right: 8px;
552
- font-size: 20px;
553
- }
554
-
555
- /* ๊ทธ๋ฆฌ๋“œ ๋ฐ ์นด๋“œ ์Šคํƒ€์ผ */
556
- .grid {
557
- display: grid;
558
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
559
- gap: 24px;
560
- margin-top: 36px;
561
- }
562
-
563
- .card {
564
- background: var(--card-bg);
565
- border-radius: var(--radius-md);
566
- cursor: pointer;
567
- box-shadow: var(--shadow-sm);
568
- width: 100%;
569
- height: 280px;
570
- position: relative;
571
- display: flex;
572
- flex-direction: column;
573
- align-items: center;
574
- justify-content: center;
575
- transition: var(--transition);
576
- overflow: hidden;
577
- }
578
-
579
- .card::before {
580
- content: '';
581
- position: absolute;
582
- top: 0;
583
- left: 0;
584
- width: 100%;
585
- height: 100%;
586
- background: linear-gradient(135deg, var(--secondary-color) 0%, var(--primary-color) 100%);
587
- opacity: 0.06;
588
- z-index: 1;
589
- }
590
-
591
- .card::after {
592
- content: '';
593
- position: absolute;
594
- top: 0;
595
- left: 0;
596
- width: 100%;
597
- height: 30%;
598
- background: linear-gradient(to bottom, rgba(255,255,255,0.8) 0%, rgba(255,255,255,0) 100%);
599
- z-index: 2;
600
- }
601
-
602
- .card img {
603
- width: 65%;
604
- height: auto;
605
- object-fit: contain;
606
- position: absolute;
607
- top: 50%;
608
- left: 50%;
609
- transform: translate(-50%, -65%);
610
- border: 1px solid rgba(0,0,0,0.05);
611
- box-shadow: 0 4px 15px rgba(0,0,0,0.08);
612
- z-index: 3;
613
- transition: var(--transition);
614
- }
615
-
616
- .card:hover {
617
- transform: translateY(-5px);
618
- box-shadow: var(--shadow-md);
619
- }
620
-
621
- .card:hover img {
622
- transform: translate(-50%, -65%) scale(1.03);
623
- box-shadow: 0 8px 20px rgba(0,0,0,0.12);
624
- }
625
-
626
- .card p {
627
- position: absolute;
628
- bottom: 20px;
629
- left: 50%;
630
- transform: translateX(-50%);
631
- background: rgba(255, 255, 255, 0.9);
632
- padding: 8px 16px;
633
- border-radius: 30px;
634
- box-shadow: 0 2px 10px rgba(0,0,0,0.05);
635
- width: 80%;
636
- text-align: center;
637
- white-space: nowrap;
638
- overflow: hidden;
639
- text-overflow: ellipsis;
640
- font-size: 14px;
641
- font-weight: 500;
642
- color: var(--text-color);
643
- z-index: 4;
644
- transition: var(--transition);
645
- }
646
-
647
- .card:hover p {
648
- background: rgba(255, 255, 255, 0.95);
649
- box-shadow: 0 4px 12px rgba(0,0,0,0.08);
650
- }
651
-
652
- /* ์บ์‹œ ์ƒํƒœ ๋ฑƒ์ง€ */
653
- .cached-status {
654
- position: absolute;
655
- top: 10px;
656
- right: 10px;
657
- background: var(--accent-color);
658
- color: white;
659
- font-size: 11px;
660
- padding: 3px 8px;
661
- border-radius: 12px;
662
- z-index: 5;
663
- box-shadow: var(--shadow-sm);
664
- }
665
-
666
- /* ๋ทฐ์–ด ์Šคํƒ€์ผ */
667
- #viewer {
668
- width: 90%;
669
- height: 90vh;
670
- max-width: 90%;
671
- margin: 0;
672
- background: var(--card-bg);
673
- border: none;
674
- border-radius: var(--radius-lg);
675
- position: fixed;
676
- top: 50%;
677
- left: 50%;
678
- transform: translate(-50%, -50%);
679
- z-index: 1000;
680
- box-shadow: var(--shadow-lg);
681
- max-height: calc(90vh - 40px);
682
- aspect-ratio: auto;
683
- object-fit: contain;
684
- overflow: hidden;
685
- }
686
-
687
- /* FlipBook ์ปจํŠธ๋กค๋ฐ” ์Šคํƒ€์ผ */
688
- .flipbook-container .fb3d-menu-bar {
689
- z-index: 2000 !important;
690
- opacity: 1 !important;
691
- bottom: 0 !important;
692
- background-color: rgba(255,255,255,0.9) !important;
693
- backdrop-filter: blur(10px) !important;
694
- border-radius: 0 0 var(--radius-lg) var(--radius-lg) !important;
695
- padding: 12px 0 !important;
696
- box-shadow: 0 -4px 20px rgba(0,0,0,0.1) !important;
697
- }
698
-
699
- .flipbook-container .fb3d-menu-bar > ul > li > img,
700
- .flipbook-container .fb3d-menu-bar > ul > li > div {
701
- opacity: 1 !important;
702
- transform: scale(1.2) !important;
703
- filter: drop-shadow(0 2px 3px rgba(0,0,0,0.1)) !important;
704
- }
705
-
706
- .flipbook-container .fb3d-menu-bar > ul > li {
707
- margin: 0 12px !important;
708
- }
709
-
710
- /* ๋ฉ”๋‰ด ํˆดํŒ ์Šคํƒ€์ผ */
711
- .flipbook-container .fb3d-menu-bar > ul > li > span {
712
- background-color: rgba(0,0,0,0.7) !important;
713
- color: white !important;
714
- border-radius: var(--radius-sm) !important;
715
- padding: 8px 12px !important;
716
- font-size: 13px !important;
717
- bottom: 55px !important;
718
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif !important;
719
- letter-spacing: 0.3px !important;
720
- }
721
-
722
- /* ๋ทฐ์–ด ๋ชจ๋“œ์ผ ๋•Œ ๋ฐฐ๊ฒฝ ์˜ค๋ฒ„๋ ˆ์ด */
723
- .viewer-mode {
724
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important;
725
- }
726
-
727
- /* ๋ทฐ์–ด ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ */
728
- #viewerPage {
729
- background: transparent;
730
- }
731
-
732
- /* ๋กœ๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
733
- @keyframes spin {
734
- 0% { transform: rotate(0deg); }
735
- 100% { transform: rotate(360deg); }
736
- }
737
-
738
- .loading-spinner {
739
- border: 4px solid rgba(255,255,255,0.3);
740
- border-top: 4px solid var(--primary-color);
741
- border-radius: 50%;
742
- width: 50px;
743
- height: 50px;
744
- margin: 0 auto;
745
- animation: spin 1.5s ease-in-out infinite;
746
- }
747
-
748
- .loading-container {
749
- position: absolute;
750
- top: 50%;
751
- left: 50%;
752
- transform: translate(-50%, -50%);
753
- text-align: center;
754
- background: rgba(255, 255, 255, 0.85);
755
- backdrop-filter: blur(10px);
756
- padding: 30px;
757
- border-radius: var(--radius-md);
758
- box-shadow: var(--shadow-md);
759
- z-index: 9999;
760
- }
761
-
762
- .loading-text {
763
- margin-top: 20px;
764
- font-size: 16px;
765
- color: var(--text-color);
766
- font-weight: 500;
767
- }
768
-
769
- /* ํŽ˜์ด์ง€ ์ „ํ™˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ */
770
- @keyframes fadeIn {
771
- from { opacity: 0; }
772
- to { opacity: 1; }
773
- }
774
-
775
- .fade-in {
776
- animation: fadeIn 0.5s ease-out;
777
- }
778
-
779
- /* ์ถ”๊ฐ€ ์Šคํƒ€์ผ */
780
- .section-title {
781
- font-size: 1.3rem;
782
- font-weight: 600;
783
- margin: 30px 0 15px;
784
- color: var(--text-color);
785
- }
786
-
787
- .no-projects {
788
- text-align: center;
789
- margin: 40px 0;
790
- color: var(--text-color);
791
- font-size: 16px;
792
- }
793
-
794
- /* ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” */
795
- .progress-bar-container {
796
- width: 100%;
797
- height: 6px;
798
- background-color: rgba(0,0,0,0.1);
799
- border-radius: 3px;
800
- margin-top: 15px;
801
- overflow: hidden;
802
- }
803
-
804
- .progress-bar {
805
- height: 100%;
806
- background: linear-gradient(to right, var(--primary-color), var(--accent-color));
807
- border-radius: 3px;
808
- transition: width 0.3s ease;
809
- }
810
-
811
- /* ํ—ค๋” ๋กœ๊ณ  ๋ฐ ํƒ€์ดํ‹€ */
812
- .library-header {
813
- position: fixed;
814
- top: 12px;
815
- left: 0;
816
- right: 0;
817
- text-align: center;
818
- z-index: 100;
819
- pointer-events: none;
820
- }
821
-
822
- .library-header .title {
823
- display: inline-block;
824
- padding: 8px 24px; /* ํŒจ๋”ฉ ์ถ•์†Œ */
825
- background: rgba(255, 255, 255, 0.85);
826
- backdrop-filter: blur(10px);
827
- border-radius: 25px; /* ํ…Œ๋‘๋ฆฌ ๋ชจ์„œ๋ฆฌ ์ถ•์†Œ */
828
- box-shadow: var(--shadow-md);
829
- font-size: 1.25rem; /* ๊ธ€์ž ํฌ๊ธฐ ์ถ•์†Œ (1.5rem์—์„œ 1.25rem์œผ๋กœ) */
830
- font-weight: 600;
831
- background-image: linear-gradient(120deg, #8e74eb 0%, #9d66ff 100%); /* ์ œ๋ชฉ ์ƒ‰์ƒ๋„ ๋ฐ”ํƒ•ํ™”๋ฉด๊ณผ ์–ด์šธ๋ฆฌ๊ฒŒ ๋ณ€๊ฒฝ */
832
- -webkit-background-clip: text;
833
- background-clip: text;
834
- color: transparent;
835
- pointer-events: all;
836
- }
837
-
838
- /* ์ ์ง„์  ๋กœ๋”ฉ ํ‘œ์‹œ */
839
- .loading-pages {
840
- position: absolute;
841
- bottom: 20px;
842
- left: 50%;
843
- transform: translateX(-50%);
844
- background: rgba(255, 255, 255, 0.9);
845
- padding: 10px 20px;
846
- border-radius: 20px;
847
- box-shadow: var(--shadow-md);
848
- font-size: 14px;
849
- color: var(--text-color);
850
- z-index: 9998;
851
- text-align: center;
852
- }
853
-
854
- /* ๋ฐ˜์‘ํ˜• ๋””์ž์ธ */
855
- @media (max-width: 768px) {
856
- .grid {
857
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
858
- gap: 16px;
859
- }
860
-
861
- .card {
862
- height: 240px;
863
- }
864
-
865
- .library-header .title {
866
- font-size: 1.25rem;
867
- padding: 10px 20px;
868
- }
869
-
870
- .floating-home {
871
- width: 50px;
872
- height: 50px;
873
- }
874
-
875
- .floating-home .icon {
876
- font-size: 18px;
877
- }
878
- }
879
- </style>
880
- </head>
881
- <body>
882
- <!-- ์ œ๋ชฉ์„ Home ๋ฒ„ํŠผ๊ณผ ํ•จ๊ป˜ ๋ ˆ์ด์–ด๋กœ ์ฒ˜๋ฆฌ -->
883
- <div id="homeButton" class="floating-home" style="display:none;">
884
- <div class="icon"><i class="fas fa-home"></i></div>
885
- <div class="title">ํ™ˆ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ</div>
886
- </div>
887
-
888
- <!-- ์„ผํ„ฐ ์ •๋ ฌ๋œ ํƒ€์ดํ‹€ -->
889
- <div class="library-header">
890
- <div class="title">FlipBook Maker</div>
891
- </div>
892
-
893
- <section id="home" class="fade-in">
894
- <div class="upload-container">
895
- <button class="upload" id="pdfUploadBtn">
896
- <i class="fas fa-file-pdf"></i> PDF ์ถ”๊ฐ€
897
- </button>
898
- <input id="pdfInput" type="file" accept="application/pdf" style="display:none">
899
- </div>
900
-
901
- <div class="section-title">๋‚ด ํ”„๋กœ์ ํŠธ</div>
902
- <div class="grid" id="grid">
903
- <!-- ์นด๋“œ๊ฐ€ ์—ฌ๊ธฐ์— ๋™์ ์œผ๋กœ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค -->
904
- </div>
905
- <div id="noProjects" class="no-projects" style="display: none;">
906
- ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. PDF๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ์‹œ์ž‘ํ•˜์„ธ์š”.
907
- </div>
908
- </section>
909
-
910
- <section id="viewerPage" style="display:none">
911
- <div id="viewer"></div>
912
- <div id="loadingPages" class="loading-pages" style="display:none;">ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘... <span id="loadingPagesCount">0/0</span></div>
913
- </section>
914
-
915
- <script>
916
- let projects=[], fb=null;
917
- const grid=document.getElementById('grid'), viewer=document.getElementById('viewer');
918
- pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
919
-
920
- // ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ๋กœ๋“œ๋œ PDF ํ”„๋กœ์ ํŠธ
921
- let serverProjects = [];
922
-
923
- // ํ˜„์žฌ ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ƒํƒœ
924
- let currentLoadingPdfPath = null;
925
- let pageLoadingInterval = null;
926
-
927
- /* ๐Ÿ”Š ์˜ค๋””์˜ค unlock โ€“ ๋‚ด์žฅ Audio ์™€ ๊ฐ™์€ MP3 ๊ฒฝ๋กœ ์‚ฌ์šฉ */
928
- ['click','touchstart'].forEach(evt=>{
929
- document.addEventListener(evt,function u(){new Audio('static/turnPage2.mp3')
930
- .play().then(a=>a.pause()).catch(()=>{});document.removeEventListener(evt,u,{capture:true});},
931
- {once:true,capture:true});
932
- });
933
-
934
- /* โ”€โ”€ ์œ ํ‹ธ โ”€โ”€ */
935
- function $id(id){return document.getElementById(id)}
936
-
937
- // ํŒŒ์ผ ์—…๋กœ๋“œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
938
- document.addEventListener("DOMContentLoaded", function() {
939
- console.log("DOM ๋กœ๋“œ ์™„๋ฃŒ, ์ด๋ฒคํŠธ ์„ค์ • ์‹œ์ž‘");
940
-
941
- // PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ
942
- const pdfBtn = document.getElementById('pdfUploadBtn');
943
- const pdfInput = document.getElementById('pdfInput');
944
-
945
- if (pdfBtn && pdfInput) {
946
- console.log("PDF ์—…๋กœ๋“œ ๋ฒ„ํŠผ ์ฐพ์Œ");
947
-
948
- // ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํŒŒ์ผ ์ž…๋ ฅ ํŠธ๋ฆฌ๊ฑฐ
949
- pdfBtn.addEventListener('click', function() {
950
- console.log("PDF ๋ฒ„ํŠผ ํด๋ฆญ๋จ");
951
- pdfInput.click();
952
- });
953
-
954
- // ํŒŒ์ผ ์„ ํƒ ์‹œ ์ฒ˜๋ฆฌ
955
- pdfInput.addEventListener('change', function(e) {
956
- console.log("PDF ํŒŒ์ผ ์„ ํƒ๋จ:", e.target.files.length);
957
- const file = e.target.files[0];
958
- if (!file) return;
959
-
960
- showLoading("PDF ๋กœ๋”ฉ ์ค‘...");
961
-
962
- const reader = new FileReader();
963
- reader.onload = function(event) {
964
- pdfjsLib.getDocument({data: event.target.result}).promise.then(async function(pdf) {
965
- const pages = [];
966
-
967
- for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
968
- updateLoading(`PDF ํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ค‘... (${pageNum}/${pdf.numPages})`);
969
-
970
- const page = await pdf.getPage(pageNum);
971
- const viewport = page.getViewport({scale: 1});
972
- const canvas = document.createElement('canvas');
973
- canvas.width = viewport.width;
974
- canvas.height = viewport.height;
975
-
976
- await page.render({
977
- canvasContext: canvas.getContext('2d'),
978
- viewport: viewport
979
- }).promise;
980
-
981
- pages.push({
982
- src: canvas.toDataURL(),
983
- thumb: canvas.toDataURL()
984
- });
985
- }
986
-
987
- hideLoading();
988
- save(pages, file.name.replace('.pdf', ''));
989
- }).catch(function(error) {
990
- console.error("PDF ๋กœ๋”ฉ ์˜ค๋ฅ˜:", error);
991
- hideLoading();
992
- showError("PDF ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: " + error.message);
993
- });
994
- };
995
-
996
- reader.onerror = function(error) {
997
- console.error("ํŒŒ์ผ ์ฝ๊ธฐ ์˜ค๋ฅ˜:", error);
998
- hideLoading();
999
- showError("ํŒŒ์ผ์„ ์ฝ๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1000
- };
1001
-
1002
- reader.readAsArrayBuffer(file);
1003
- });
1004
- } else {
1005
- console.error("PDF ์—…๋กœ๋“œ ์š”์†Œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Œ");
1006
- }
1007
-
1008
- // ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์บ์‹œ ์ƒํƒœ ํ™•์ธ
1009
- loadServerPDFs();
1010
-
1011
- // ์บ์‹œ ์ƒํƒœ๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ™•์ธ
1012
- setInterval(checkCacheStatus, 3000);
1013
- });
1014
-
1015
- function addCard(i, thumb, title, isCached = false) {
1016
- const d = document.createElement('div');
1017
- d.className = 'card fade-in';
1018
- d.onclick = () => open(i);
1019
-
1020
- // ์ œ๋ชฉ ์ฒ˜๋ฆฌ
1021
- const displayTitle = title ?
1022
- (title.length > 15 ? title.substring(0, 15) + '...' : title) :
1023
- 'ํ”„๋กœ์ ํŠธ ' + (i+1);
1024
-
1025
- // ์บ์‹œ ์ƒํƒœ ๋ฑƒ์ง€ ์ถ”๊ฐ€
1026
- const cachedBadge = isCached ?
1027
- '<div class="cached-status">์บ์‹œ๋จ</div>' : '';
1028
-
1029
- d.innerHTML = `
1030
- <div class="card-inner">
1031
- ${cachedBadge}
1032
- <img src="${thumb}" alt="${displayTitle}" loading="lazy">
1033
- <p title="${title || 'ํ”„๋กœ์ ํŠธ ' + (i+1)}">${displayTitle}</p>
1034
- </div>
1035
- `;
1036
- grid.appendChild(d);
1037
-
1038
- // ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ์œผ๋ฉด 'ํ”„๋กœ์ ํŠธ ์—†์Œ' ๋ฉ”์‹œ์ง€ ์ˆจ๊ธฐ๊ธฐ
1039
- $id('noProjects').style.display = 'none';
1040
- }
1041
-
1042
- /* โ”€โ”€ ํ”„๋กœ์ ํŠธ ์ €์žฅ โ”€โ”€ */
1043
- function save(pages, title, isCached = false){
1044
- const id=projects.push(pages)-1;
1045
- addCard(id, pages[0].thumb, title, isCached);
1046
- }
1047
-
1048
- /* โ”€โ”€ ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์บ์‹œ ์ƒํƒœ ํ™•์ธ โ”€โ”€ */
1049
- async function loadServerPDFs() {
1050
- try {
1051
- // ๋กœ๋”ฉ ํ‘œ์‹œ ์ถ”๊ฐ€
1052
- if (document.querySelectorAll('.card').length === 0) {
1053
- showLoading("๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋”ฉ ์ค‘...");
1054
- }
1055
-
1056
- // ๋จผ์ € ์บ์‹œ ์ƒํƒœ ํ™•์ธ
1057
- const cacheStatusRes = await fetch('/api/cache-status');
1058
- const cacheStatus = await cacheStatusRes.json();
1059
-
1060
- // PDF ํ”„๋กœ์ ํŠธ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
1061
- const response = await fetch('/api/pdf-projects');
1062
- serverProjects = await response.json();
1063
-
1064
- if (serverProjects.length === 0) {
1065
- hideLoading();
1066
- $id('noProjects').style.display = 'block';
1067
- return;
1068
- }
1069
-
1070
- // ์„œ๋ฒ„ PDF ๋กœ๋“œ ๋ฐ ์ธ๋„ค์ผ ์ƒ์„ฑ (๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ์ตœ์ ํ™”)
1071
- const thumbnailPromises = serverProjects.map(async (project, index) => {
1072
- updateLoading(`PDF ํ”„๋กœ์ ํŠธ ๋กœ๋”ฉ ์ค‘... (${index+1}/${serverProjects.length})`);
1073
-
1074
- const pdfName = project.name;
1075
- const isCached = cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed";
1076
-
1077
- try {
1078
- // ์ธ๋„ค์ผ ๊ฐ€์ ธ์˜ค๊ธฐ
1079
- const response = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(project.path)}`);
1080
- const data = await response.json();
1081
-
1082
- if(data.thumbnail) {
1083
- const pages = [{
1084
- src: data.thumbnail,
1085
- thumb: data.thumbnail,
1086
- path: project.path,
1087
- cached: isCached
1088
- }];
1089
-
1090
- return {
1091
- pages,
1092
- name: project.name,
1093
- isCached
1094
- };
1095
- }
1096
- } catch (err) {
1097
- console.error(`์ธ๋„ค์ผ ๋กœ๋“œ ์˜ค๋ฅ˜ (${project.name}):`, err);
1098
- }
1099
-
1100
- return null;
1101
- });
1102
-
1103
- // ๋ชจ๋“  ์ธ๋„ค์ผ ์š”์ฒญ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ
1104
- const results = await Promise.all(thumbnailPromises);
1105
-
1106
- // ์„ฑ๊ณต์ ์œผ๋กœ ๊ฐ€์ ธ์˜จ ๊ฒฐ๊ณผ๋งŒ ํ‘œ์‹œ
1107
- results.filter(result => result !== null).forEach(result => {
1108
- save(result.pages, result.name, result.isCached);
1109
- });
1110
-
1111
- hideLoading();
1112
-
1113
- // ํ”„๋กœ์ ํŠธ๊ฐ€ ์—†์„ ๊ฒฝ์šฐ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
1114
- if (document.querySelectorAll('.card').length === 0) {
1115
- $id('noProjects').style.display = 'block';
1116
- }
1117
- } catch(error) {
1118
- console.error('์„œ๋ฒ„ PDF ๋กœ๋“œ ์‹คํŒจ:', error);
1119
- hideLoading();
1120
- showError("๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๋”ฉ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1121
- }
1122
- }
1123
-
1124
- /* โ”€โ”€ ์บ์‹œ ์ƒํƒœ ์ •๊ธฐ์ ์œผ๋กœ ํ™•์ธ โ”€โ”€ */
1125
- async function checkCacheStatus() {
1126
- try {
1127
- const response = await fetch('/api/cache-status');
1128
- const cacheStatus = await response.json();
1129
-
1130
- // ํ˜„์žฌ ์นด๋“œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
1131
- const cards = document.querySelectorAll('.card');
1132
-
1133
- for(let i = 0; i < cards.length; i++) {
1134
- if(projects[i] && projects[i][0] && projects[i][0].path) {
1135
- const pdfPath = projects[i][0].path;
1136
- const pdfName = pdfPath.split('/').pop().replace('.pdf', '');
1137
-
1138
- // ์บ์‹œ ์ƒํƒœ ๋ฑƒ์ง€ ์—…๋ฐ์ดํŠธ
1139
- let badgeEl = cards[i].querySelector('.cached-status');
1140
-
1141
- if(cacheStatus[pdfName] && cacheStatus[pdfName].status === "completed") {
1142
- if(!badgeEl) {
1143
- badgeEl = document.createElement('div');
1144
- badgeEl.className = 'cached-status';
1145
- badgeEl.textContent = '์บ์‹œ๋จ';
1146
- cards[i].querySelector('.card-inner')?.appendChild(badgeEl);
1147
- } else if (badgeEl.textContent !== '์บ์‹œ๋จ') {
1148
- badgeEl.textContent = '์บ์‹œ๋จ';
1149
- badgeEl.style.background = 'var(--accent-color)';
1150
- }
1151
- projects[i][0].cached = true;
1152
- } else if(cacheStatus[pdfName] && cacheStatus[pdfName].status === "processing") {
1153
- if(!badgeEl) {
1154
- badgeEl = document.createElement('div');
1155
- badgeEl.className = 'cached-status';
1156
- cards[i].querySelector('.card-inner')?.appendChild(badgeEl);
1157
- }
1158
- badgeEl.textContent = `${cacheStatus[pdfName].progress}%`;
1159
- badgeEl.style.background = 'var(--secondary-color)';
1160
- }
1161
- }
1162
- }
1163
-
1164
- // ํ˜„์žฌ ๋กœ๋”ฉ ์ค‘์ธ PDF๊ฐ€ ์žˆ์œผ๋ฉด ์ƒํƒœ ํ™•์ธ
1165
- if (currentLoadingPdfPath && pageLoadingInterval) {
1166
- const pdfName = currentLoadingPdfPath.split('/').pop().replace('.pdf', '');
1167
-
1168
- if (cacheStatus[pdfName]) {
1169
- const status = cacheStatus[pdfName].status;
1170
- const progress = cacheStatus[pdfName].progress || 0;
1171
-
1172
- if (status === "completed") {
1173
- // ์บ์‹ฑ ์™„๋ฃŒ ์‹œ
1174
- clearInterval(pageLoadingInterval);
1175
- $id('loadingPages').style.display = 'none';
1176
- currentLoadingPdfPath = null;
1177
-
1178
- // ์™„๋ฃŒ๋œ ์บ์‹œ๋กœ ํ”Œ๋ฆฝ๋ถ ๋‹ค์‹œ ๋กœ๋“œ
1179
- refreshFlipBook();
1180
- } else if (status === "processing") {
1181
- // ์ง„ํ–‰ ์ค‘์ผ ๋•Œ ํ‘œ์‹œ ์—…๋ฐ์ดํŠธ
1182
- $id('loadingPages').style.display = 'block';
1183
- $id('loadingPagesCount').textContent = `${progress}%`;
1184
- }
1185
- }
1186
- }
1187
-
1188
- } catch(error) {
1189
- console.error('์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:', error);
1190
- }
1191
- }
1192
-
1193
- /* โ”€โ”€ ์นด๋“œ โ†’ FlipBook โ”€โ”€ */
1194
- async function open(i) {
1195
- toggle(false);
1196
- const pages = projects[i];
1197
-
1198
- // ๊ธฐ์กด FlipBook ์ •๋ฆฌ
1199
- if(fb) {
1200
- fb.destroy();
1201
- viewer.innerHTML = '';
1202
- }
1203
-
1204
- // ์„œ๋ฒ„ PDF ๋˜๋Š” ๋กœ์ปฌ ํ”„๋กœ์ ํŠธ ์ฒ˜๋ฆฌ
1205
- if(pages[0].path) {
1206
- const pdfPath = pages[0].path;
1207
-
1208
- // ์ ์ง„์  ๋กœ๋”ฉ ํ”Œ๋ž˜๊ทธ ์ดˆ๊ธฐํ™”
1209
- let progressiveLoading = false;
1210
- currentLoadingPdfPath = pdfPath;
1211
-
1212
- // ์บ์‹œ ์—ฌ๋ถ€ ํ™•์ธ
1213
- if(pages[0].cached) {
1214
- // ์บ์‹œ๋œ PDF ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
1215
- showLoading("์บ์‹œ๋œ PDF ๋กœ๋”ฉ ์ค‘...");
1216
-
1217
- try {
1218
- const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(pdfPath)}`);
1219
- const cachedData = await response.json();
1220
-
1221
- if(cachedData.status === "completed" && cachedData.pages) {
1222
- hideLoading();
1223
- createFlipBook(cachedData.pages);
1224
- currentLoadingPdfPath = null;
1225
- return;
1226
- } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
1227
- // ์ผ๋ถ€ ํŽ˜์ด์ง€๊ฐ€ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ ์ ์ง„์  ๋กœ๋”ฉ ์‚ฌ์šฉ
1228
- hideLoading();
1229
- createFlipBook(cachedData.pages);
1230
- progressiveLoading = true;
1231
-
1232
- // ์ ์ง„์  ๋กœ๋”ฉ ์ค‘์ž„์„ ํ‘œ์‹œ
1233
- startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1234
- }
1235
- } catch(error) {
1236
- console.error("์บ์‹œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ ์˜ค๋ฅ˜:", error);
1237
- // ์บ์‹œ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ์›๋ณธ PDF๋กœ ๋Œ€์ฒด
1238
- }
1239
- }
1240
-
1241
- if (!progressiveLoading) {
1242
- // ์บ์‹œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋กœ๋”ฉ ์‹คํŒจ ์‹œ ์„œ๋ฒ„ PDF ๋กœ๋“œ
1243
- showLoading("PDF ์ค€๋น„ ์ค‘...");
1244
-
1245
- try {
1246
- const response = await fetch(`/api/pdf-content?path=${encodeURIComponent(pdfPath)}`);
1247
- const data = await response.json();
1248
-
1249
- // ์บ์‹œ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋œ ๊ฒฝ์šฐ
1250
- if(data.redirect) {
1251
- const redirectRes = await fetch(data.redirect);
1252
- const cachedData = await redirectRes.json();
1253
-
1254
- if(cachedData.status === "completed" && cachedData.pages) {
1255
- hideLoading();
1256
- createFlipBook(cachedData.pages);
1257
- currentLoadingPdfPath = null;
1258
- return;
1259
- } else if(cachedData.status === "processing" && cachedData.pages && cachedData.pages.length > 0) {
1260
- // ์ผ๋ถ€ ํŽ˜์ด์ง€๊ฐ€ ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ ์ ์ง„์  ๋กœ๋”ฉ ์‚ฌ์šฉ
1261
- hideLoading();
1262
- createFlipBook(cachedData.pages);
1263
-
1264
- // ์ ์ง„์  ๋กœ๋”ฉ ์ค‘์ž„์„ ํ‘œ์‹œ
1265
- startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1266
- return;
1267
- }
1268
- }
1269
-
1270
- // ์›๋ณธ PDF ๋กœ๋“œ (ArrayBuffer ํ˜•ํƒœ)
1271
- const pdfResponse = await fetch(`/api/pdf-content?path=${encodeURIComponent(pdfPath)}`);
1272
-
1273
- // JSON ์‘๋‹ต์ธ ๊ฒฝ์šฐ (๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋“ฑ)
1274
- try {
1275
- const jsonData = await pdfResponse.clone().json();
1276
- if (jsonData.redirect) {
1277
- const redirectRes = await fetch(jsonData.redirect);
1278
- const cachedData = await redirectRes.json();
1279
-
1280
- if(cachedData.pages && cachedData.pages.length > 0) {
1281
- hideLoading();
1282
- createFlipBook(cachedData.pages);
1283
-
1284
- if(cachedData.status === "processing") {
1285
- startProgressiveLoadingIndicator(cachedData.progress, cachedData.total_pages);
1286
- } else {
1287
- currentLoadingPdfPath = null;
1288
- }
1289
- return;
1290
- }
1291
- }
1292
- } catch (e) {
1293
- // JSON ํŒŒ์‹ฑ ์‹คํŒจ ์‹œ ์›๋ณธ PDF ๋ฐ์ดํ„ฐ๋กœ ์ฒ˜๋ฆฌ
1294
- }
1295
-
1296
- // ArrayBuffer ํ˜•ํƒœ์˜ PDF ๋ฐ์ดํ„ฐ
1297
- const pdfData = await pdfResponse.arrayBuffer();
1298
-
1299
- // PDF ๋กœ๋“œ ๋ฐ ํŽ˜์ด์ง€ ๋ Œ๋”๋ง
1300
- const pdf = await pdfjsLib.getDocument({data: pdfData}).promise;
1301
- const pdfPages = [];
1302
-
1303
- for(let p = 1; p <= pdf.numPages; p++) {
1304
- updateLoading(`ํŽ˜์ด์ง€ ์ค€๋น„ ์ค‘... (${p}/${pdf.numPages})`);
1305
-
1306
- const pg = await pdf.getPage(p);
1307
- const vp = pg.getViewport({scale: 1});
1308
- const c = document.createElement('canvas');
1309
- c.width = vp.width;
1310
- c.height = vp.height;
1311
-
1312
- await pg.render({canvasContext: c.getContext('2d'), viewport: vp}).promise;
1313
- pdfPages.push({src: c.toDataURL(), thumb: c.toDataURL()});
1314
- }
1315
-
1316
- hideLoading();
1317
- createFlipBook(pdfPages);
1318
- currentLoadingPdfPath = null;
1319
-
1320
- } catch(error) {
1321
- console.error('PDF ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
1322
- hideLoading();
1323
- showError("PDF๋ฅผ ๋กœ๋“œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1324
- currentLoadingPdfPath = null;
1325
- }
1326
- }
1327
- } else {
1328
- // ๋กœ์ปฌ ์—…๋กœ๋“œ๋œ ํ”„๋กœ์ ํŠธ ์‹คํ–‰
1329
- createFlipBook(pages);
1330
- currentLoadingPdfPath = null;
1331
- }
1332
- }
1333
-
1334
- /* โ”€โ”€ ์ ์ง„์  ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์‹œ์ž‘ โ”€โ”€ */
1335
- function startProgressiveLoadingIndicator(progress, totalPages) {
1336
- // ์ง„ํ–‰ ์ƒํƒœ ํ‘œ์‹œ ํ™œ์„ฑํ™”
1337
- $id('loadingPages').style.display = 'block';
1338
- $id('loadingPagesCount').textContent = `${progress}%`;
1339
-
1340
- // ๊ธฐ์กด ์ธํ„ฐ๋ฒŒ ์ œ๊ฑฐ
1341
- if (pageLoadingInterval) {
1342
- clearInterval(pageLoadingInterval);
1343
- }
1344
-
1345
- // ์ฃผ๊ธฐ์ ์œผ๋กœ ์บ์‹œ ์ƒํƒœ ํ™•์ธ (2์ดˆ๋งˆ๋‹ค)
1346
- pageLoadingInterval = setInterval(async () => {
1347
- if (!currentLoadingPdfPath) {
1348
- clearInterval(pageLoadingInterval);
1349
- $id('loadingPages').style.display = 'none';
1350
- return;
1351
- }
1352
-
1353
- try {
1354
- const response = await fetch(`/api/cache-status?path=${encodeURIComponent(currentLoadingPdfPath)}`);
1355
- const status = await response.json();
1356
-
1357
- // ์ƒํƒœ ์—…๋ฐ์ดํŠธ
1358
- if (status.status === "completed") {
1359
- clearInterval(pageLoadingInterval);
1360
- $id('loadingPages').style.display = 'none';
1361
- refreshFlipBook(); // ์™„๋ฃŒ๋œ ๋ฐ์ดํ„ฐ๋กœ ์ƒˆ๋กœ๊ณ ์นจ
1362
- currentLoadingPdfPath = null;
1363
- } else if (status.status === "processing") {
1364
- $id('loadingPagesCount').textContent = `${status.progress}%`;
1365
- }
1366
- } catch (e) {
1367
- console.error("์บ์‹œ ์ƒํƒœ ํ™•์ธ ์˜ค๋ฅ˜:", e);
1368
- }
1369
- }, 1000);
1370
- }
1371
-
1372
- /* โ”€โ”€ ํ”Œ๋ฆฝ๋ถ ์ƒˆ๋กœ๊ณ ์นจ โ”€โ”€ */
1373
- async function refreshFlipBook() {
1374
- if (!currentLoadingPdfPath || !fb) return;
1375
-
1376
- try {
1377
- const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(currentLoadingPdfPath)}`);
1378
- const cachedData = await response.json();
1379
-
1380
- if(cachedData.status === "completed" && cachedData.pages) {
1381
- // ๊ธฐ์กด ํ”Œ๋ฆฝ๋ถ ์ •๋ฆฌ
1382
- fb.destroy();
1383
- viewer.innerHTML = '';
1384
-
1385
- // ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ ์žฌ์ƒ์„ฑ
1386
- createFlipBook(cachedData.pages);
1387
- currentLoadingPdfPath = null;
1388
- }
1389
- } catch (e) {
1390
- console.error("ํ”Œ๋ฆฝ๋ถ ์ƒˆ๋กœ๊ณ ์นจ ์˜ค๋ฅ˜:", e);
1391
- }
1392
- }
1393
-
1394
- function createFlipBook(pages) {
1395
- console.log('FlipBook ์ƒ์„ฑ ์‹œ์ž‘. ํŽ˜์ด์ง€ ์ˆ˜:', pages.length);
1396
-
1397
- try {
1398
- // ํ™”๋ฉด ๋น„์œจ ๊ณ„์‚ฐ
1399
- const calculateAspectRatio = () => {
1400
- const windowWidth = window.innerWidth;
1401
- const windowHeight = window.innerHeight;
1402
- const aspectRatio = windowWidth / windowHeight;
1403
-
1404
- // ๋„ˆ๋น„ ๋˜๋Š” ๋†’์ด ๊ธฐ์ค€์œผ๋กœ ์ตœ๋Œ€ 90% ์ œํ•œ
1405
- let width, height;
1406
- if (aspectRatio > 1) { // ๊ฐ€๋กœ ํ™”๋ฉด
1407
- height = Math.min(windowHeight * 0.9, windowHeight - 40);
1408
- width = height * aspectRatio * 0.8; // ๊ฐ€๋กœ ํ™”๋ฉด์—์„œ๋Š” ์•ฝ๊ฐ„ ์ค„์ž„
1409
- if (width > windowWidth * 0.9) {
1410
- width = windowWidth * 0.9;
1411
- height = width / (aspectRatio * 0.8);
1412
- }
1413
- } else { // ์„ธ๋กœ ํ™”๋ฉด
1414
- width = Math.min(windowWidth * 0.9, windowWidth - 40);
1415
- height = width / aspectRatio * 0.9; // ์„ธ๋กœ ํ™”๋ฉด์—์„œ๋Š” ์•ฝ๊ฐ„ ๋Š˜๋ฆผ
1416
- if (height > windowHeight * 0.9) {
1417
- height = windowHeight * 0.9;
1418
- width = height * aspectRatio * 0.9;
1419
- }
1420
- }
1421
-
1422
- // ์ตœ์  ์‚ฌ์ด์ฆˆ ๋ฐ˜ํ™˜
1423
- return {
1424
- width: Math.round(width),
1425
- height: Math.round(height)
1426
- };
1427
- };
1428
-
1429
- // ์ดˆ๊ธฐ ํ™”๋ฉด ๋น„์œจ ๊ณ„์‚ฐ
1430
- const size = calculateAspectRatio();
1431
- viewer.style.width = size.width + 'px';
1432
- viewer.style.height = size.height + 'px';
1433
-
1434
- // ํŽ˜์ด์ง€ ๋ฐ์ดํ„ฐ ์ •์ œ (๋นˆ ํŽ˜์ด์ง€ ์ฒ˜๋ฆฌ)
1435
- const validPages = pages.map(page => {
1436
- // src๊ฐ€ ์—†๋Š” ํŽ˜์ด์ง€๋Š” ๋กœ๋”ฉ ์ค‘ ์ด๋ฏธ์ง€๋กœ ๋Œ€์ฒด
1437
- if (!page || !page.src) {
1438
- return {
1439
- src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iIGZpbGw9IiM1NTUiPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+',
1440
- thumb: page && page.thumb ? page.thumb : ''
1441
- };
1442
- }
1443
- return page;
1444
- });
1445
-
1446
- fb = new FlipBook(viewer, {
1447
- pages: validPages,
1448
- viewMode: 'webgl',
1449
- autoSize: true,
1450
- flipDuration: 800,
1451
- backgroundColor: '#fff',
1452
- /* ๐Ÿ”Š ๋‚ด์žฅ ์‚ฌ์šด๋“œ */
1453
- sound: true,
1454
- assets: {flipMp3: 'static/turnPage2.mp3', hardFlipMp3: 'static/turnPage2.mp3'},
1455
- controlsProps: {
1456
- enableFullscreen: true,
1457
- enableToc: true,
1458
- enableDownload: false,
1459
- enablePrint: false,
1460
- enableZoom: true,
1461
- enableShare: false,
1462
- enableSearch: true,
1463
- enableAutoPlay: true,
1464
- enableAnnotation: false,
1465
- enableSound: true,
1466
- enableLightbox: false,
1467
- layout: 10, // ๋ ˆ์ด์•„์›ƒ ์˜ต์…˜
1468
- skin: 'light', // ์Šคํ‚จ ์Šคํƒ€์ผ
1469
- autoNavigationTime: 3600, // ์ž๋™ ๋„˜๊น€ ์‹œ๊ฐ„(์ดˆ)
1470
- hideControls: false, // ์ปจํŠธ๋กค ์ˆจ๊น€ ๋น„ํ™œ์„ฑํ™”
1471
- paddingTop: 10, // ์ƒ๋‹จ ํŒจ๋”ฉ
1472
- paddingLeft: 10, // ์ขŒ์ธก ํŒจ๋”ฉ
1473
- paddingRight: 10, // ์šฐ์ธก ํŒจ๋”ฉ
1474
- paddingBottom: 10, // ํ•˜๋‹จ ํŒจ๋”ฉ
1475
- pageTextureSize: 1024, // ํŽ˜์ด์ง€ ํ…์Šค์ฒ˜ ํฌ๊ธฐ
1476
- thumbnails: true, // ์„ฌ๋„ค์ผ ํ™œ์„ฑํ™”
1477
- autoHideControls: false, // ์ž๋™ ์ˆจ๊น€ ๋น„ํ™œ์„ฑํ™”
1478
- controlsTimeout: 8000 // ์ปจํŠธ๋กค ํ‘œ์‹œ ์‹œ๊ฐ„ ์—ฐ์žฅ
1479
- }
1480
- });
1481
-
1482
- // ํ™”๋ฉด ํฌ๊ธฐ ๋ณ€๊ฒฝ ์‹œ FlipBook ํฌ๊ธฐ ์กฐ์ •
1483
- window.addEventListener('resize', () => {
1484
- if (fb) {
1485
- const newSize = calculateAspectRatio();
1486
- viewer.style.width = newSize.width + 'px';
1487
- viewer.style.height = newSize.height + 'px';
1488
- fb.resize();
1489
- }
1490
- });
1491
-
1492
- // FlipBook ์ƒ์„ฑ ํ›„ ์ปจํŠธ๋กค๋ฐ” ๊ฐ•์ œ ํ‘œ์‹œ
1493
- setTimeout(() => {
1494
- try {
1495
- // ์ปจํŠธ๋กค๋ฐ” ๊ด€๋ จ ์š”์†Œ ์ฐพ๊ธฐ ๋ฐ ์Šคํƒ€์ผ ์ ์šฉ
1496
- const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar');
1497
- if (menuBars && menuBars.length > 0) {
1498
- menuBars.forEach(menuBar => {
1499
- menuBar.style.display = 'block';
1500
- menuBar.style.opacity = '1';
1501
- menuBar.style.visibility = 'visible';
1502
- menuBar.style.zIndex = '9999';
1503
- });
1504
- }
1505
- } catch (e) {
1506
- console.warn('์ปจํŠธ๋กค๋ฐ” ์Šคํƒ€์ผ ์ ์šฉ ์ค‘ ์˜ค๋ฅ˜:', e);
1507
- }
1508
- }, 1000);
1509
-
1510
- console.log('FlipBook ์ƒ์„ฑ ์™„๋ฃŒ');
1511
- } catch (error) {
1512
- console.error('FlipBook ์ƒ์„ฑ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ:', error);
1513
- showError("FlipBook์„ ์ƒ์„ฑํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
1514
- }
1515
- }
1516
-
1517
- /* โ”€โ”€ ๋„ค๋น„๊ฒŒ์ด์…˜ โ”€โ”€ */
1518
- $id('homeButton').onclick=()=>{
1519
- if(fb) {
1520
- fb.destroy();
1521
- viewer.innerHTML = '';
1522
- fb = null;
1523
- }
1524
- toggle(true);
1525
-
1526
- // ๋กœ๋”ฉ ์ธ๋””์ผ€์ดํ„ฐ ์ •๋ฆฌ
1527
- if (pageLoadingInterval) {
1528
- clearInterval(pageLoadingInterval);
1529
- pageLoadingInterval = null;
1530
- }
1531
- $id('loadingPages').style.display = 'none';
1532
- currentLoadingPdfPath = null;
1533
- };
1534
-
1535
- function toggle(showHome){
1536
- $id('home').style.display=showHome?'block':'none';
1537
- $id('viewerPage').style.display=showHome?'none':'block';
1538
- $id('homeButton').style.display=showHome?'none':'block';
1539
-
1540
- // ๋ทฐ์–ด ๋ชจ๋“œ์ผ ๋•Œ ์Šคํƒ€์ผ ๋ณ€๊ฒฝ
1541
- if(!showHome) {
1542
- document.body.classList.add('viewer-mode');
1543
- } else {
1544
- document.body.classList.remove('viewer-mode');
1545
- }
1546
- }
1547
-
1548
- /* -- ๋กœ๋”ฉ ๋ฐ ์˜ค๋ฅ˜ ํ‘œ์‹œ -- */
1549
- function showLoading(message, progress = -1) {
1550
- // ๊ธฐ์กด ๋กœ๋”ฉ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ฑฐ
1551
- hideLoading();
1552
-
1553
- const loadingContainer = document.createElement('div');
1554
- loadingContainer.className = 'loading-container fade-in';
1555
- loadingContainer.id = 'loadingContainer';
1556
-
1557
- let progressBarHtml = '';
1558
- if (progress >= 0) {
1559
- progressBarHtml = `
1560
- <div class="progress-bar-container">
1561
- <div id="progressBar" class="progress-bar" style="width: ${progress}%;"></div>
1562
- </div>
1563
- `;
1564
- }
1565
-
1566
- loadingContainer.innerHTML = `
1567
- <div class="loading-spinner"></div>
1568
- <p class="loading-text" id="loadingText">${message || '๋กœ๋”ฉ ์ค‘...'}</p>
1569
- ${progressBarHtml}
1570
- `;
1571
-
1572
- document.body.appendChild(loadingContainer);
1573
- }
1574
-
1575
- function updateLoading(message, progress = -1) {
1576
- const loadingText = $id('loadingText');
1577
- if (loadingText) {
1578
- loadingText.textContent = message;
1579
- }
1580
-
1581
- if (progress >= 0) {
1582
- let progressBar = $id('progressBar');
1583
-
1584
- if (!progressBar) {
1585
- const loadingContainer = $id('loadingContainer');
1586
- if (loadingContainer) {
1587
- const progressContainer = document.createElement('div');
1588
- progressContainer.className = 'progress-bar-container';
1589
- progressContainer.innerHTML = `<div id="progressBar" class="progress-bar" style="width: ${progress}%;"></div>`;
1590
- loadingContainer.appendChild(progressContainer);
1591
- progressBar = $id('progressBar');
1592
- }
1593
- } else {
1594
- progressBar.style.width = `${progress}%`;
1595
- }
1596
- }
1597
- }
1598
-
1599
- function hideLoading() {
1600
- const loadingContainer = $id('loadingContainer');
1601
- if (loadingContainer) {
1602
- loadingContainer.remove();
1603
- }
1604
- }
1605
-
1606
- function showError(message) {
1607
- // ๊ธฐ์กด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์žˆ๋‹ค๋ฉด ์ œ๊ฑฐ
1608
- const existingError = $id('errorContainer');
1609
- if (existingError) {
1610
- existingError.remove();
1611
- }
1612
-
1613
- const errorContainer = document.createElement('div');
1614
- errorContainer.className = 'loading-container fade-in';
1615
- errorContainer.id = 'errorContainer';
1616
- errorContainer.innerHTML = `
1617
- <p class="loading-text" style="color: #e74c3c;">${message}</p>
1618
- <button id="errorCloseBtn" style="margin-top: 15px; padding: 8px 16px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer;">ํ™•์ธ</button>
1619
- `;
1620
-
1621
- document.body.appendChild(errorContainer);
1622
-
1623
- // ํ™•์ธ ๋ฒ„ํŠผ ํด๋ฆญ ์ด๋ฒคํŠธ
1624
- $id('errorCloseBtn').onclick = () => {
1625
- errorContainer.remove();
1626
- };
1627
-
1628
- // 5์ดˆ ํ›„ ์ž๋™์œผ๋กœ ๋‹ซ๊ธฐ
1629
- setTimeout(() => {
1630
- if ($id('errorContainer')) {
1631
- $id('errorContainer').remove();
1632
- }
1633
- }, 5000);
1634
- }
1635
- </script>
1636
- </body>
1637
- </html>
1638
- """
1639
-
1640
- @app.get("/", response_class=HTMLResponse)
1641
- async def root():
1642
- return get_html_content()
1643
-
1644
- if __name__ == "__main__":
1645
- uvicorn.run("app:app", host="0.0.0.0", port=int(os.getenv("PORT", 7860)))