Skip to content

Commit

Permalink
Merge pull request #5 from kougen/feat/timestamped-names
Browse files Browse the repository at this point in the history
Added timestamps to filenames
  • Loading branch information
joshika39 authored Feb 6, 2025
2 parents f57626c + 7d45499 commit 99022f0
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 32 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ COPY main.py main.py

COPY templates templates
COPY static static
COPY assets assets

RUN pip install -r requirements.txt

Expand Down
Binary file added assets/Roboto-Medium.ttf
Binary file not shown.
71 changes: 45 additions & 26 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
from fastapi import FastAPI, Request, Form, UploadFile, File
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.templating import Jinja2Templates
from typing import List
from typing import List, Optional
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
import io
import uvicorn
from dotenv import load_dotenv
from datetime import datetime

load_dotenv()

Expand All @@ -19,9 +20,20 @@
STATIC_DIR = Path("static/js")
STATIC_DIR.mkdir(exist_ok=True, parents=True)

WORD_FILE_NAME = os.getenv("WORD_FILE_NAME", "flashcards_words.pdf")
IMAGE_FILE_NAME = os.getenv("IMAGE_FILE_NAME", "flashcards.pdf")

SERVER_URL = os.getenv("SERVER_URL", "http://localhost:8000")


def get_timestamp():
return datetime.now().strftime("%Y%m%d%H%M%S")


def get_timestamped_filename(file_name: str):
return f"{get_timestamp()}_{file_name}"


@app.get("/", response_class=HTMLResponse)
async def homepage(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
Expand All @@ -46,8 +58,9 @@ async def error(request: Request, error_type: int | None = None):
@app.post("/generate-pdf", response_class=HTMLResponse)
async def generate_pdf(
request: Request,
images: List[UploadFile] = File(...),
words: List[str] = Form(...)
words: List[str] = Form(...),
images: Optional[List[UploadFile]] = Form(None),
exclude_images: Optional[bool] = Form(False)
):
num_cards = len(words)
grid_size = (3, 5)
Expand All @@ -58,27 +71,15 @@ async def generate_pdf(
pdf_words = []

for i in range(num_cards):
img = Image.open(io.BytesIO(await images[i].read()))
img.thumbnail(card_size) # Keep aspect ratio
pdf_images.append(img)
if not exclude_images:
img = Image.open(io.BytesIO(await images[i].read()))
img.thumbnail(card_size)
pdf_images.append(img)
pdf_words.append(words[i])

pdf_path = UPLOAD_DIR / "flashcards.pdf"
pdf = Image.new("RGB", pdf_size, "white")
draw = ImageDraw.Draw(pdf)
font = ImageFont.load_default()

for i, img in enumerate(pdf_images):
x = (i % grid_size[0]) * card_size[0]
y = (i // grid_size[0]) * card_size[1]
img_x = x + (card_size[0] - img.width) // 2
img_y = y + (card_size[1] - img.height) // 2
pdf.paste(img, (img_x, img_y))
draw.rectangle([x, y, x + card_size[0], y + card_size[1]], outline="black", width=2) # Draw border
font = ImageFont.truetype('./assets/Roboto-Medium.ttf', 30)

pdf.save(pdf_path, "PDF")

pdf_words_path = UPLOAD_DIR / "flashcards_words.pdf"
pdf_words_path = UPLOAD_DIR / get_timestamped_filename(WORD_FILE_NAME)
word_pdf = Image.new("RGB", pdf_size, "white")
draw = ImageDraw.Draw(word_pdf)

Expand All @@ -95,11 +96,29 @@ async def generate_pdf(

word_pdf.save(pdf_words_path, "PDF")

return templates.TemplateResponse("pdf_link.html", {
"request": request,
"pdf_url": f"/uploads/flashcards.pdf",
"pdf_words_url": f"/uploads/flashcards_words.pdf"
})
body = {
"pdf_words_url": f"/uploads/{pdf_words_path.name}",
}

if not exclude_images:
pdf_path = UPLOAD_DIR / get_timestamped_filename(IMAGE_FILE_NAME)
pdf = Image.new("RGB", pdf_size, "white")
draw = ImageDraw.Draw(pdf)

for i, img in enumerate(pdf_images):
x = (i % grid_size[0]) * card_size[0]
y = (i // grid_size[0]) * card_size[1]
img_x = x + (card_size[0] - img.width) // 2
img_y = y + (card_size[1] - img.height) // 2
pdf.paste(img, (img_x, img_y))
draw.rectangle([x, y, x + card_size[0], y + card_size[1]], outline="black", width=2) # Draw border

pdf.save(pdf_path, "PDF")
body["pdf_url"] = f"/uploads/{pdf_path.name}"

body["request"] = request

return templates.TemplateResponse("pdf_link.html", body)


@app.get("/uploads/{file_name}")
Expand Down
18 changes: 15 additions & 3 deletions static/js/utility.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
let activeRow = null;

document.getElementById('excludeImages').addEventListener('change', function() {
document.getElementById('image-column').style.display = this.checked ? 'none' : '';
});
window.onload = function () {
document.getElementById('exclude-images').addEventListener('change', function () {
document.getElementById('image-header').style.display = this.checked ? 'none' : '';

const imageInputs = document.querySelectorAll('.image-input');
const imageCells = document.querySelectorAll('.image-cell');
imageInputs.forEach((input) => {
input.required = !this.checked;
});
imageCells.forEach((cell) => {
cell.style.display = this.checked ? 'none' : '';
});
});

}

document.addEventListener("click", (event) => {
const row = event.target.closest("tr");
Expand Down
6 changes: 4 additions & 2 deletions templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
</head>
<body class="p-10 bg-gray-100">
<div class="max-w-4xl mx-auto bg-white p-6 shadow-md rounded-lg">

<h1 class="text-2xl font-bold mb-4">Flashcard Generator</h1>

{% if error_type == 404 %}
Expand All @@ -19,16 +20,17 @@ <h1 class="text-2xl font-bold mb-4">Flashcard Generator</h1>
<div hx-get="/error" hx-swap="outerHTML" hx-trigger="load"
hx-vals='{"error_message": "{{ error_message }}"}'></div>
{% else %}

<form id="flashcard-form" hx-post="/generate-pdf" hx-target="#pdf-link" enctype="multipart/form-data">
<label>
<input type="checkbox" id="excludeImages" name="exclude_images">
<input type="checkbox" id="exclude-images" name="exclude_images">
Exclude Images
</label>

<table class="w-full border-collapse border border-gray-300" id="flashcard-table">
<thead>
<tr>
<th class="border p-2" id="image-column">Image</th>
<th class="border p-2" id="image-header">Image</th>
<th class="border p-2">Word</th>
<th class="border p-2">Actions</th>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion templates/row.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<tr class="editable">
<td class="border p-2"><input type="file" name="images" accept="image/*" required></td>
<td class="border p-2 image-cell"><input class="image-input" type="file" name="images" accept="image/*" required></td>
<td class="border p-2"><input type="text" name="words" class="border p-1 w-full" required></td>
<td class="border p-2 text-center">
<button type="button" class="text-red-500" onclick="this.closest('tr').remove()">Delete</button>
Expand Down

0 comments on commit 99022f0

Please sign in to comment.