Compare commits
3 Commits
edefce9703
...
21c9bfd359
| Author | SHA1 | Date | |
|---|---|---|---|
|
21c9bfd359
|
|||
|
3d720ff426
|
|||
|
d9dd1281c3
|
+62
-136
@@ -1,12 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import os
|
|
||||||
import io
|
import io
|
||||||
import boto3
|
|
||||||
import zipfile
|
import zipfile
|
||||||
import tempfile
|
import tempfile
|
||||||
from botocore.exceptions import ClientError
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from fastapi import FastAPI, Request, Form
|
from fastapi import FastAPI, Request, Form
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
|
||||||
@@ -19,6 +16,16 @@ import logging
|
|||||||
# Import the functions from gen_op.py
|
# Import the functions from gen_op.py
|
||||||
from app.gen_op import generer_pdf_exercices_en_memoire
|
from app.gen_op import generer_pdf_exercices_en_memoire
|
||||||
|
|
||||||
|
# Import S3 functions
|
||||||
|
from app.s3_utils import (
|
||||||
|
S3_BUCKET_NAME,
|
||||||
|
create_bucket_if_not_exists,
|
||||||
|
upload_to_s3,
|
||||||
|
download_from_s3,
|
||||||
|
list_objects_in_s3,
|
||||||
|
delete_from_s3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Custom filter to exclude health check logs
|
# Custom filter to exclude health check logs
|
||||||
class HealthCheckFilter(logging.Filter):
|
class HealthCheckFilter(logging.Filter):
|
||||||
@@ -32,53 +39,9 @@ logging.getLogger("uvicorn.access").addFilter(HealthCheckFilter())
|
|||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# S3 Configuration
|
|
||||||
S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", "math-exercises")
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
|
||||||
def get_s3_client():
|
|
||||||
"""Create and return an S3 client using environment variables"""
|
|
||||||
s3_access_key = os.environ.get("S3_ACCESS_KEY")
|
|
||||||
s3_secret_key = os.environ.get("S3_SECRET_KEY")
|
|
||||||
s3_host_base = os.environ.get("S3_HOST_BASE")
|
|
||||||
|
|
||||||
if not all([s3_access_key, s3_secret_key, s3_host_base]):
|
|
||||||
raise ValueError("S3 environment variables not properly set")
|
|
||||||
|
|
||||||
s3 = boto3.client(
|
|
||||||
"s3",
|
|
||||||
aws_access_key_id=s3_access_key,
|
|
||||||
aws_secret_access_key=s3_secret_key,
|
|
||||||
endpoint_url=s3_host_base,
|
|
||||||
region_name="us-east-1", # Required but unused for Infomaniak
|
|
||||||
)
|
|
||||||
return s3
|
|
||||||
|
|
||||||
|
|
||||||
def create_bucket_if_not_exists(bucket_name):
|
|
||||||
"""Create S3 bucket if it doesn't exist"""
|
|
||||||
s3_client = get_s3_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
s3_client.head_bucket(Bucket=bucket_name)
|
|
||||||
except ClientError as e:
|
|
||||||
error_code = int(e.response["Error"]["Code"])
|
|
||||||
if error_code == 404:
|
|
||||||
# Bucket doesn't exist, create it
|
|
||||||
try:
|
|
||||||
s3_client.create_bucket(Bucket=bucket_name)
|
|
||||||
print(f"Bucket {bucket_name} created successfully")
|
|
||||||
except ClientError as create_error:
|
|
||||||
print(f"Error creating bucket: {create_error}")
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
# Some other error
|
|
||||||
print(f"Error checking bucket: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
# Create bucket on startup
|
# Create bucket on startup
|
||||||
try:
|
try:
|
||||||
create_bucket_if_not_exists(S3_BUCKET_NAME)
|
create_bucket_if_not_exists(S3_BUCKET_NAME)
|
||||||
@@ -86,67 +49,6 @@ except Exception as e:
|
|||||||
print(f"Warning: Could not create/check S3 bucket: {e}")
|
print(f"Warning: Could not create/check S3 bucket: {e}")
|
||||||
|
|
||||||
|
|
||||||
def upload_to_s3(file_data, bucket_name, object_name, content_type="application/pdf"):
|
|
||||||
"""Upload file data to S3 bucket"""
|
|
||||||
s3_client = get_s3_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
s3_client.put_object(
|
|
||||||
Bucket=bucket_name,
|
|
||||||
Key=object_name,
|
|
||||||
Body=file_data,
|
|
||||||
ContentType=content_type,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except ClientError as e:
|
|
||||||
print(f"Error uploading to S3: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def download_from_s3(bucket_name, object_name):
|
|
||||||
"""Download file data from S3 bucket"""
|
|
||||||
s3_client = get_s3_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = s3_client.get_object(Bucket=bucket_name, Key=object_name)
|
|
||||||
return response["Body"].read()
|
|
||||||
except ClientError as e:
|
|
||||||
print(f"Error downloading from S3: {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def list_objects_in_s3(bucket_name):
|
|
||||||
"""List all objects in S3 bucket (sorted from newest to oldest)"""
|
|
||||||
s3_client = get_s3_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = s3_client.list_objects_v2(Bucket=bucket_name)
|
|
||||||
if "Contents" in response:
|
|
||||||
# Filter for PDF files only and sort by last modified (newest first)
|
|
||||||
pdf_files = [
|
|
||||||
obj for obj in response["Contents"] if obj["Key"].endswith(".pdf")
|
|
||||||
]
|
|
||||||
pdf_files.sort(key=lambda x: x["LastModified"], reverse=True)
|
|
||||||
return pdf_files
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
except ClientError as e:
|
|
||||||
print(f"Error listing objects in S3: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def delete_from_s3(bucket_name, object_name):
|
|
||||||
"""Delete file from S3 bucket"""
|
|
||||||
s3_client = get_s3_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
s3_client.delete_object(Bucket=bucket_name, Key=object_name)
|
|
||||||
return True
|
|
||||||
except ClientError as e:
|
|
||||||
print(f"Error deleting from S3: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class MathExercisesPDF(FPDF):
|
class MathExercisesPDF(FPDF):
|
||||||
def header(self):
|
def header(self):
|
||||||
self.set_font("Helvetica", "B", 16)
|
self.set_font("Helvetica", "B", 16)
|
||||||
@@ -168,6 +70,7 @@ class ExerciseRequest(BaseModel):
|
|||||||
max_table: int
|
max_table: int
|
||||||
num_exercises: int = 15
|
num_exercises: int = 15
|
||||||
multiplication_only: bool = False
|
multiplication_only: bool = False
|
||||||
|
max_first_operand: int = 12 # New parameter for the maximum value of the first operand
|
||||||
|
|
||||||
|
|
||||||
class OperationExerciseRequest(BaseModel):
|
class OperationExerciseRequest(BaseModel):
|
||||||
@@ -175,7 +78,11 @@ class OperationExerciseRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
def generate_exercises(
|
def generate_exercises(
|
||||||
min_table: int, max_table: int, num_exercises: int = 15, multiplication_only: bool = False
|
min_table: int,
|
||||||
|
max_table: int,
|
||||||
|
num_exercises: int = 15,
|
||||||
|
multiplication_only: bool = False,
|
||||||
|
max_first_operand: int = 12, # New parameter for the maximum value of the first operand
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
"""Génère des exercices de multiplication et division aléatoires mélangés sans doublons"""
|
||||||
exercises: List[str] = []
|
exercises: List[str] = []
|
||||||
@@ -188,27 +95,28 @@ def generate_exercises(
|
|||||||
while len(exercises) < num_exercises and attempts < max_attempts:
|
while len(exercises) < num_exercises and attempts < max_attempts:
|
||||||
attempts += 1
|
attempts += 1
|
||||||
|
|
||||||
# Choisir deux nombres aléatoires entre min_table et max_table
|
# Pour les multiplications, un des opérandes est entre 1 et max_first_operand
|
||||||
a = random.randint(min_table, max_table)
|
# et l'autre est entre min_table et max_table
|
||||||
b = random.randint(min_table, max_table)
|
if multiplication_only or random.choice([True, False]):
|
||||||
result = a * b
|
|
||||||
|
|
||||||
# Déterminer le type d'exercice
|
|
||||||
if multiplication_only:
|
|
||||||
exercise_type = "multiplication"
|
|
||||||
else:
|
|
||||||
# Choisir aléatoirement le type d'exercice (seulement multiplication ou division)
|
|
||||||
exercise_type = random.choice(["multiplication", "division"])
|
|
||||||
|
|
||||||
if exercise_type == "multiplication":
|
|
||||||
# Exercice de multiplication
|
# Exercice de multiplication
|
||||||
operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération
|
a = random.randint(1, max_first_operand) # Premier opérande entre 1 et max_first_operand
|
||||||
|
b = random.randint(min_table, max_table) # Second opérande entre min_table et max_table
|
||||||
|
|
||||||
|
# Randomize the order of operands
|
||||||
|
if random.choice([True, False]):
|
||||||
|
a, b = b, a
|
||||||
|
|
||||||
|
result = a * b
|
||||||
|
operation_key = f"mult_{min(a,b)}_{max(a,b)}" # Clé unique pour cette opération (order-independent)
|
||||||
if operation_key not in used_operations:
|
if operation_key not in used_operations:
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
exercises.append(exercise)
|
exercises.append(exercise)
|
||||||
used_operations.add(operation_key)
|
used_operations.add(operation_key)
|
||||||
elif not multiplication_only: # division
|
elif not multiplication_only: # division
|
||||||
# Exercice de division
|
# Exercice de division
|
||||||
|
a = random.randint(min_table, max_table)
|
||||||
|
b = random.randint(min_table, max_table)
|
||||||
|
result = a * b
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération
|
operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération
|
||||||
if operation_key not in used_operations:
|
if operation_key not in used_operations:
|
||||||
@@ -222,24 +130,32 @@ def generate_exercises(
|
|||||||
remaining = num_exercises - len(exercises)
|
remaining = num_exercises - len(exercises)
|
||||||
for _ in range(remaining):
|
for _ in range(remaining):
|
||||||
# Générer des variations avec des nombres légèrement différents
|
# Générer des variations avec des nombres légèrement différents
|
||||||
a = random.randint(min_table, max_table)
|
# Pour les multiplications, un des opérandes est entre 1 et max_first_operand
|
||||||
b = random.randint(min_table, max_table)
|
# et l'autre est entre min_table et max_table
|
||||||
result = a * b
|
if multiplication_only or random.choice([True, False]):
|
||||||
|
# Exercice de multiplication
|
||||||
|
a = random.randint(1, max_first_operand) # Premier opérande entre 1 et max_first_operand
|
||||||
|
b = random.randint(min_table, max_table) # Second opérande entre min_table et max_table
|
||||||
|
|
||||||
# Déterminer le type d'exercice
|
# Randomize the order of operands
|
||||||
if multiplication_only:
|
if random.choice([True, False]):
|
||||||
exercise_type = "multiplication"
|
a, b = b, a
|
||||||
else:
|
|
||||||
# Choisir aléatoirement le type d'exercice
|
|
||||||
exercise_type = random.choice(["multiplication", "division"])
|
|
||||||
|
|
||||||
if exercise_type == "multiplication":
|
result = a * b
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
elif not multiplication_only: # division
|
elif not multiplication_only: # division
|
||||||
|
a = random.randint(min_table, max_table)
|
||||||
|
b = random.randint(min_table, max_table)
|
||||||
|
result = a * b
|
||||||
divisor = random.choice([a, b])
|
divisor = random.choice([a, b])
|
||||||
exercise = f"{result} : {divisor} = ____"
|
exercise = f"{result} : {divisor} = ____"
|
||||||
else:
|
else:
|
||||||
# Fallback to multiplication if multiplication_only is True
|
# Fallback to multiplication if multiplication_only is True
|
||||||
|
a = random.randint(1, max_first_operand)
|
||||||
|
b = random.randint(min_table, max_table)
|
||||||
|
# Randomize the order of operands
|
||||||
|
if random.choice([True, False]):
|
||||||
|
a, b = b, a
|
||||||
exercise = f"{a} · {b} = ____"
|
exercise = f"{a} · {b} = ____"
|
||||||
|
|
||||||
exercises.append(exercise)
|
exercises.append(exercise)
|
||||||
@@ -248,7 +164,11 @@ def generate_exercises(
|
|||||||
|
|
||||||
|
|
||||||
def create_math_exercises_pdf(
|
def create_math_exercises_pdf(
|
||||||
min_table: int, max_table: int, num_exercises: int = 15, multiplication_only: bool = False
|
min_table: int,
|
||||||
|
max_table: int,
|
||||||
|
num_exercises: int = 15,
|
||||||
|
multiplication_only: bool = False,
|
||||||
|
max_first_operand: int = 12, # New parameter for the maximum value of the first operand
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes et l'upload sur S3"""
|
"""Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes et l'upload sur S3"""
|
||||||
import datetime
|
import datetime
|
||||||
@@ -284,7 +204,9 @@ def create_math_exercises_pdf(
|
|||||||
pdf.ln(5)
|
pdf.ln(5)
|
||||||
|
|
||||||
# Générer les exercices
|
# Générer les exercices
|
||||||
exercises = generate_exercises(min_table, max_table, num_exercises, multiplication_only)
|
exercises = generate_exercises(
|
||||||
|
min_table, max_table, num_exercises, multiplication_only, max_first_operand
|
||||||
|
)
|
||||||
|
|
||||||
# Pas d'en-têtes de colonnes
|
# Pas d'en-têtes de colonnes
|
||||||
|
|
||||||
@@ -376,7 +298,11 @@ async def generate_exercises_endpoint(request: ExerciseRequest):
|
|||||||
return {"error": "Le nombre d'exercices doit être supérieur à 0"}
|
return {"error": "Le nombre d'exercices doit être supérieur à 0"}
|
||||||
|
|
||||||
pdf_filename = create_math_exercises_pdf(
|
pdf_filename = create_math_exercises_pdf(
|
||||||
request.min_table, request.max_table, request.num_exercises, request.multiplication_only
|
request.min_table,
|
||||||
|
request.max_table,
|
||||||
|
request.num_exercises,
|
||||||
|
request.multiplication_only,
|
||||||
|
request.max_first_operand, # Pass the new parameter
|
||||||
)
|
)
|
||||||
|
|
||||||
# Return redirect to automatically download the file
|
# Return redirect to automatically download the file
|
||||||
|
|||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import boto3
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
# S3 Configuration
|
||||||
|
S3_BUCKET_NAME = os.environ.get("S3_BUCKET_NAME", "math-exercises")
|
||||||
|
|
||||||
|
|
||||||
|
def get_s3_client():
|
||||||
|
"""Create and return an S3 client using environment variables"""
|
||||||
|
s3_access_key = os.environ.get("S3_ACCESS_KEY")
|
||||||
|
s3_secret_key = os.environ.get("S3_SECRET_KEY")
|
||||||
|
s3_host_base = os.environ.get("S3_HOST_BASE")
|
||||||
|
|
||||||
|
if not all([s3_access_key, s3_secret_key, s3_host_base]):
|
||||||
|
raise ValueError("S3 environment variables not properly set")
|
||||||
|
|
||||||
|
s3 = boto3.client(
|
||||||
|
"s3",
|
||||||
|
aws_access_key_id=s3_access_key,
|
||||||
|
aws_secret_access_key=s3_secret_key,
|
||||||
|
endpoint_url=s3_host_base,
|
||||||
|
region_name="us-east-1", # Required but unused for Infomaniak
|
||||||
|
)
|
||||||
|
return s3
|
||||||
|
|
||||||
|
|
||||||
|
def create_bucket_if_not_exists(bucket_name: str):
|
||||||
|
"""Create S3 bucket if it doesn't exist"""
|
||||||
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3_client.head_bucket(Bucket=bucket_name)
|
||||||
|
except ClientError as e:
|
||||||
|
error_code = int(e.response["Error"]["Code"])
|
||||||
|
if error_code == 404:
|
||||||
|
# Bucket doesn't exist, create it
|
||||||
|
try:
|
||||||
|
s3_client.create_bucket(Bucket=bucket_name)
|
||||||
|
print(f"Bucket {bucket_name} created successfully")
|
||||||
|
except ClientError as create_error:
|
||||||
|
print(f"Error creating bucket: {create_error}")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# Some other error
|
||||||
|
print(f"Error checking bucket: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def upload_to_s3(
|
||||||
|
file_data: bytes,
|
||||||
|
bucket_name: str,
|
||||||
|
object_name: str,
|
||||||
|
content_type: str = "application/pdf",
|
||||||
|
) -> bool:
|
||||||
|
"""Upload file data to S3 bucket"""
|
||||||
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3_client.put_object(
|
||||||
|
Bucket=bucket_name,
|
||||||
|
Key=object_name,
|
||||||
|
Body=file_data,
|
||||||
|
ContentType=content_type,
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"Error uploading to S3: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def download_from_s3(bucket_name: str, object_name: str) -> Optional[bytes]:
|
||||||
|
"""Download file data from S3 bucket"""
|
||||||
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = s3_client.get_object(Bucket=bucket_name, Key=object_name)
|
||||||
|
return response["Body"].read()
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"Error downloading from S3: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def list_objects_in_s3(bucket_name: str) -> List[dict]:
|
||||||
|
"""List all objects in S3 bucket (sorted from newest to oldest)"""
|
||||||
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = s3_client.list_objects_v2(Bucket=bucket_name)
|
||||||
|
if "Contents" in response:
|
||||||
|
# Filter for PDF files only and sort by last modified (newest first)
|
||||||
|
pdf_files = [
|
||||||
|
obj for obj in response["Contents"] if obj["Key"].endswith(".pdf")
|
||||||
|
]
|
||||||
|
pdf_files.sort(key=lambda x: x["LastModified"], reverse=True)
|
||||||
|
return pdf_files
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"Error listing objects in S3: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def delete_from_s3(bucket_name: str, object_name: str) -> bool:
|
||||||
|
"""Delete file from S3 bucket"""
|
||||||
|
s3_client = get_s3_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3_client.delete_object(Bucket=bucket_name, Key=object_name)
|
||||||
|
return True
|
||||||
|
except ClientError as e:
|
||||||
|
print(f"Error deleting from S3: {e}")
|
||||||
|
return False
|
||||||
@@ -66,6 +66,16 @@
|
|||||||
<label class="form-check-label" for="multiplicationOnly">Générer uniquement des multiplications</label>
|
<label class="form-check-label" for="multiplicationOnly">Générer uniquement des multiplications</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="maxFirstOperand" class="form-label">Maximum du premier opérande</label>
|
||||||
|
<select class="form-select" id="maxFirstOperand">
|
||||||
|
<option value="9">9 (Tables jusqu'à 9)</option>
|
||||||
|
<option value="10" selected>10 (Tables jusqu'à 10)</option>
|
||||||
|
<option value="12">12 (Tables jusqu'à 12)</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">La valeur maximale pour le premier opérande dans les multiplications</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="d-grid">
|
<div class="d-grid">
|
||||||
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
|
<button type="submit" class="btn btn-primary btn-lg" id="generateBtn">
|
||||||
<span id="buttonText">Générer le PDF (Mult/Div)</span>
|
<span id="buttonText">Générer le PDF (Mult/Div)</span>
|
||||||
@@ -242,6 +252,45 @@
|
|||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
|
|
||||||
|
// Save form values to localStorage
|
||||||
|
function saveFormValues() {
|
||||||
|
const exerciseFormValues = {
|
||||||
|
minTable: document.getElementById('minTable').value,
|
||||||
|
maxTable: document.getElementById('maxTable').value,
|
||||||
|
numExercises: document.getElementById('numExercises').value,
|
||||||
|
multiplicationOnly: document.getElementById('multiplicationOnly').checked,
|
||||||
|
maxFirstOperand: document.getElementById('maxFirstOperand').value
|
||||||
|
};
|
||||||
|
|
||||||
|
const operationFormValues = {
|
||||||
|
numOperationExercises: document.getElementById('numOperationExercises').value
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem('exerciseFormValues', JSON.stringify(exerciseFormValues));
|
||||||
|
localStorage.setItem('operationFormValues', JSON.stringify(operationFormValues));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore form values from localStorage
|
||||||
|
function restoreFormValues() {
|
||||||
|
// Restore exercise form values
|
||||||
|
const savedExerciseValues = localStorage.getItem('exerciseFormValues');
|
||||||
|
if (savedExerciseValues) {
|
||||||
|
const exerciseValues = JSON.parse(savedExerciseValues);
|
||||||
|
document.getElementById('minTable').value = exerciseValues.minTable || 1;
|
||||||
|
document.getElementById('maxTable').value = exerciseValues.maxTable || 10;
|
||||||
|
document.getElementById('numExercises').value = exerciseValues.numExercises || 15;
|
||||||
|
document.getElementById('multiplicationOnly').checked = exerciseValues.multiplicationOnly || false;
|
||||||
|
document.getElementById('maxFirstOperand').value = exerciseValues.maxFirstOperand || 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore operation form values
|
||||||
|
const savedOperationValues = localStorage.getItem('operationFormValues');
|
||||||
|
if (savedOperationValues) {
|
||||||
|
const operationValues = JSON.parse(savedOperationValues);
|
||||||
|
document.getElementById('numOperationExercises').value = operationValues.numOperationExercises || 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Function to load and display existing PDFs with pagination (sorted from newest to oldest)
|
// Function to load and display existing PDFs with pagination (sorted from newest to oldest)
|
||||||
async function loadPdfList(page = 1) {
|
async function loadPdfList(page = 1) {
|
||||||
const refreshBtn = document.getElementById('refreshListBtn');
|
const refreshBtn = document.getElementById('refreshListBtn');
|
||||||
@@ -414,9 +463,18 @@
|
|||||||
|
|
||||||
// Load PDF list on page load
|
// Load PDF list on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
restoreFormValues();
|
||||||
loadPdfList(currentPage);
|
loadPdfList(currentPage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listeners to save form values on change
|
||||||
|
document.getElementById('minTable').addEventListener('change', saveFormValues);
|
||||||
|
document.getElementById('maxTable').addEventListener('change', saveFormValues);
|
||||||
|
document.getElementById('numExercises').addEventListener('change', saveFormValues);
|
||||||
|
document.getElementById('multiplicationOnly').addEventListener('change', saveFormValues);
|
||||||
|
document.getElementById('maxFirstOperand').addEventListener('change', saveFormValues);
|
||||||
|
document.getElementById('numOperationExercises').addEventListener('change', saveFormValues);
|
||||||
|
|
||||||
// Event listener for select all button
|
// Event listener for select all button
|
||||||
document.getElementById('selectAllBtn').addEventListener('click', function() {
|
document.getElementById('selectAllBtn').addEventListener('click', function() {
|
||||||
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
|
document.querySelectorAll('.file-checkbox').forEach(checkbox => {
|
||||||
@@ -513,10 +571,14 @@
|
|||||||
document.getElementById('exerciseForm').addEventListener('submit', async function(e) {
|
document.getElementById('exerciseForm').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Save form values to localStorage
|
||||||
|
saveFormValues();
|
||||||
|
|
||||||
const minTable = parseInt(document.getElementById('minTable').value);
|
const minTable = parseInt(document.getElementById('minTable').value);
|
||||||
const maxTable = parseInt(document.getElementById('maxTable').value);
|
const maxTable = parseInt(document.getElementById('maxTable').value);
|
||||||
const numExercises = parseInt(document.getElementById('numExercises').value);
|
const numExercises = parseInt(document.getElementById('numExercises').value);
|
||||||
const multiplicationOnly = document.getElementById('multiplicationOnly').checked;
|
const multiplicationOnly = document.getElementById('multiplicationOnly').checked;
|
||||||
|
const maxFirstOperand = parseInt(document.getElementById('maxFirstOperand').value);
|
||||||
|
|
||||||
const generateBtn = document.getElementById('generateBtn');
|
const generateBtn = document.getElementById('generateBtn');
|
||||||
const buttonText = document.getElementById('buttonText');
|
const buttonText = document.getElementById('buttonText');
|
||||||
@@ -537,7 +599,8 @@
|
|||||||
min_table: minTable,
|
min_table: minTable,
|
||||||
max_table: maxTable,
|
max_table: maxTable,
|
||||||
num_exercises: numExercises,
|
num_exercises: numExercises,
|
||||||
multiplication_only: multiplicationOnly
|
multiplication_only: multiplicationOnly,
|
||||||
|
max_first_operand: maxFirstOperand
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -588,14 +651,18 @@
|
|||||||
spinner.classList.add('d-none');
|
spinner.classList.add('d-none');
|
||||||
generateBtn.disabled = false;
|
generateBtn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle operation exercises form submission
|
// Handle operation exercises form submission
|
||||||
document.getElementById('operationExerciseForm').addEventListener('submit', async function(e) {
|
document.getElementById('operationExerciseForm').addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Save form values to localStorage
|
||||||
|
saveFormValues();
|
||||||
|
|
||||||
const numExercises = parseInt(document.getElementById('numOperationExercises').value);
|
const numExercises = parseInt(document.getElementById('numOperationExercises').value);
|
||||||
|
|
||||||
|
|
||||||
const generateBtn = document.getElementById('generateOperationBtn');
|
const generateBtn = document.getElementById('generateOperationBtn');
|
||||||
const buttonText = document.getElementById('operationButtonText');
|
const buttonText = document.getElementById('operationButtonText');
|
||||||
const spinner = document.getElementById('operationSpinner');
|
const spinner = document.getElementById('operationSpinner');
|
||||||
|
|||||||
Reference in New Issue
Block a user