|
from fastapi import FastAPI, Request |
|
from fastapi.responses import HTMLResponse |
|
from fastapi.middleware.cors import CORSMiddleware |
|
import json |
|
import os |
|
|
|
app = FastAPI() |
|
DATA_FILE = "data.json" |
|
|
|
|
|
app.add_middleware( |
|
CORSMiddleware, |
|
allow_origins=["*"], |
|
allow_methods=["*"], |
|
allow_headers=["*"], |
|
) |
|
|
|
|
|
if not os.path.exists(DATA_FILE): |
|
with open(DATA_FILE, "w") as f: |
|
json.dump([], f) |
|
|
|
@app.post("/store") |
|
async def store(request: Request): |
|
data = await request.json() |
|
with open(DATA_FILE, "w") as f: |
|
json.dump(data, f) |
|
return {"status": "success"} |
|
|
|
@app.get("/", response_class=HTMLResponse) |
|
async def index(): |
|
with open(DATA_FILE, "r") as f: |
|
stored_data = json.load(f) |
|
|
|
return HTMLResponse(content=f""" |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
<title>UniShare</title> |
|
<style> |
|
body {{ |
|
font-family: Arial, sans-serif; |
|
margin: 0; |
|
padding: 0; |
|
background: #f0f2f5; |
|
}} |
|
header {{ |
|
background: #3b82f6; |
|
color: white; |
|
padding: 1em; |
|
text-align: center; |
|
font-size: 1.8em; |
|
}} |
|
#overview {{ |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); |
|
gap: 1em; |
|
padding: 1em; |
|
}} |
|
.list-tile {{ |
|
background: white; |
|
padding: 1em; |
|
border-radius: 10px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
cursor: pointer; |
|
transition: transform 0.2s; |
|
}} |
|
.list-tile:hover {{ |
|
transform: scale(1.02); |
|
}} |
|
#list-view {{ |
|
display: none; |
|
padding: 1em; |
|
}} |
|
.back-button {{ |
|
background: #3b82f6; |
|
color: white; |
|
padding: 0.5em 1em; |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
margin-bottom: 1em; |
|
}} |
|
.card {{ |
|
background: white; |
|
padding: 1em; |
|
border-radius: 10px; |
|
box-shadow: 0 2px 5px rgba(0,0,0,0.1); |
|
margin-bottom: 1em; |
|
}} |
|
.submenu {{ |
|
margin-top: 1em; |
|
}} |
|
.submenu h4 {{ |
|
margin: 0.5em 0; |
|
}} |
|
.grid {{ |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); |
|
gap: 1em; |
|
}} |
|
.item {{ |
|
background: #f9f9f9; |
|
padding: 1em; |
|
border-radius: 10px; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.05); |
|
}} |
|
.item img {{ |
|
max-width: 100%; |
|
max-height: 150px; |
|
object-fit: cover; |
|
border-radius: 5px; |
|
margin-bottom: 0.5em; |
|
}} |
|
.item a {{ |
|
display: inline-block; |
|
margin-top: 0.5em; |
|
text-decoration: none; |
|
color: #3b82f6; |
|
}} |
|
#toast {{ |
|
position: fixed; |
|
bottom: 20px; |
|
right: 20px; |
|
background: #333; |
|
color: white; |
|
padding: 1em; |
|
border-radius: 5px; |
|
display: none; |
|
z-index: 1000; |
|
}} |
|
</style> |
|
</head> |
|
<body> |
|
<header>UniShare</header> |
|
<div id="overview"></div> |
|
<div id="list-view"> |
|
<button class="back-button" onclick="backToOverview()">← Back to Overview</button> |
|
<div id="content"></div> |
|
</div> |
|
<div id="toast"></div> |
|
|
|
<script> |
|
const overviewDiv = document.getElementById('overview'); |
|
const contentDiv = document.getElementById('content'); |
|
const listViewDiv = document.getElementById('list-view'); |
|
const toastDiv = document.getElementById('toast'); |
|
|
|
const rawData = {json.dumps(stored_data)}; |
|
const lists = {{}}; |
|
|
|
function showToast(message) {{ |
|
toastDiv.textContent = message; |
|
toastDiv.style.display = 'block'; |
|
setTimeout(() => {{ |
|
toastDiv.style.display = 'none'; |
|
}}, 3000); |
|
}} |
|
|
|
function backToOverview() {{ |
|
listViewDiv.style.display = 'none'; |
|
overviewDiv.style.display = 'grid'; |
|
}} |
|
|
|
function showList(name) {{ |
|
const sections = lists[name]; |
|
contentDiv.innerHTML = ''; |
|
|
|
sections.forEach(section => {{ |
|
const card = document.createElement('div'); |
|
card.className = 'card'; |
|
const title = document.createElement('h3'); |
|
title.textContent = section.sectionTitle; |
|
card.appendChild(title); |
|
|
|
const grid = document.createElement('div'); |
|
grid.className = 'grid'; |
|
|
|
section.items.forEach(item => {{ |
|
const div = document.createElement('div'); |
|
div.className = 'item'; |
|
let content = `<strong>${{item.title}}</strong><br>`; |
|
if (item.link.match(/\\.(jpeg|jpg|gif|png)$/)) {{ |
|
content += `<img src="${{item.link}}" alt="${{item.title}}" />`; |
|
}} |
|
content += `<a href="${{item.link}}" download>Download</a>`; |
|
div.innerHTML = content; |
|
grid.appendChild(div); |
|
}}); |
|
|
|
card.appendChild(grid); |
|
contentDiv.appendChild(card); |
|
}}); |
|
|
|
overviewDiv.style.display = 'none'; |
|
listViewDiv.style.display = 'block'; |
|
}} |
|
|
|
rawData.forEach((list, index) => {{ |
|
if (Array.isArray(list)) {{ |
|
const listName = `List ${{index + 1}}`; |
|
lists[listName] = list; |
|
const tile = document.createElement('div'); |
|
tile.className = 'list-tile'; |
|
tile.innerHTML = `<strong>${{listName}}</strong><br>${{list.length}} sections`; |
|
tile.onclick = () => showList(listName); |
|
overviewDiv.appendChild(tile); |
|
showToast(`Loaded ${{listName}}`); |
|
}} |
|
}}); |
|
|
|
if (Object.keys(lists).length === 0) {{ |
|
overviewDiv.innerHTML = '<p style="padding:1em">No valid list data available.</p>'; |
|
}} |
|
</script> |
|
</body> |
|
</html> |
|
""", status_code=200) |
|
|