import os, re, time, json, datetime, requests, gradio as gr
# ───────────────────── 1. 기본 설정 ─────────────────────
BEST_FILE, PER_PAGE = "best_games.json", 9
# ───────────────────── 2. BEST 데이터 ────────────────────
def _init_best():
if not os.path.exists(BEST_FILE):
json.dump([], open(BEST_FILE, "w"))
def _load_best():
try:
data = json.load(open(BEST_FILE))
for it in data:
if "ts" not in it:
it["ts"] = int(it.get("timestamp", time.time()))
return data
except Exception as e:
print(f"BEST 데이터 로드 오류: {e}")
return []
def _save_best(data):
try:
json.dump(data, open(BEST_FILE, "w"))
return True
except Exception as e:
print(f"BEST 데이터 저장 오류: {e}")
return False
# ───────────────────── 3. URL 추가 기능 ─────────────────────
def add_url_to_best(title, url):
"""사용자가 제공한 URL을 BEST 목록에 추가합니다."""
try:
# 현재 BEST 데이터 로드
data = _load_best()
# URL이 이미 존재하는지 확인
for item in data:
if item.get("url") == url:
print(f"URL이 이미 존재합니다: {url}")
return False
# 새 항목 추가
new_item = {
"title": title,
"url": url,
"ts": int(time.time()),
"projectId": "", # 사용자가 직접 추가하므로 projectId 없음
"deploymentId": "" # 사용자가 직접 추가하므로 deploymentId 없음
}
data.append(new_item)
# 시간순으로 정렬
data = sorted(data, key=lambda x: x["ts"], reverse=True)
# 저장
if _save_best(data):
print(f"URL이 성공적으로 추가되었습니다: {url}")
return True
return False
except Exception as e:
print(f"URL 추가 오류: {str(e)}")
return False
# ───────────────────── 4. 페이지네이션 ───────────────────
def page(lst, pg):
s = (pg-1) * PER_PAGE
e = s + PER_PAGE
total = (len(lst) + PER_PAGE - 1) // PER_PAGE
return lst[s:e], total
# ───────────────────── 5. HTML 그리드 ───────────────────
def html(cards, pg, total):
if not cards:
return "
표시할 배포가 없습니다.
"
css = r"""
"""
h = css + """
"""
for c in cards:
date = datetime.datetime.fromtimestamp(int(c["ts"])).strftime("%Y-%m-%d")
h += f"""
"""
h += """
"""
# 페이지 정보
h += f'Page {pg} / {total}
'
return h
# ───────────────────── 6. Gradio Blocks UI ───────────────────
def build():
_init_best()
with gr.Blocks(title="Vibe Game Craft", css="""
/* Fix container sizing and prevent scrolling */
html, body, .gradio-container {
height: 100vh !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
}
footer {display: none !important;}
.button-row {
position: fixed !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
background: #f0f0f0 !important;
padding: 10px !important;
text-align: center !important;
box-shadow: 0 -2px 10px rgba(0,0,0,0.05) !important;
margin: 0 !important;
z-index: 1000 !important;
}
.button-row button {
margin: 0 10px !important;
padding: 10px 20px !important;
font-size: 16px !important;
font-weight: bold !important;
border-radius: 50px !important;
}
/* Fix component height */
#component-0 {
height: calc(100vh - 60px) !important;
overflow: hidden !important;
}
""") as demo:
# 상태 및 출력
bp = gr.State(1)
out = gr.HTML()
# 페이지 네비게이션 (맨 아래 고정)
with gr.Row(elem_classes="button-row"):
b_prev = gr.Button("◀ 이전", size="lg")
b_next = gr.Button("다음 ▶", size="lg")
def show_best(p=1):
d, t = page(_load_best(), p)
return html(d, p, t), p
def prev(b):
b = max(1, b-1)
h, _ = show_best(b)
return h, b
def nxt(b):
maxp = (len(_load_best()) + PER_PAGE - 1) // PER_PAGE
b = min(maxp, b+1)
h, _ = show_best(b)
return h, b
# 이벤트 연결
b_prev.click(prev, inputs=[bp], outputs=[out, bp])
b_next.click(nxt, inputs=[bp], outputs=[out, bp])
# 초기 로드
demo.load(show_best, outputs=[out, bp])
return demo
app = build()
if __name__ == "__main__":
app.launch()