diff --git a/eduaid_web/src/assets/aossie_logo_transparent.png b/eduaid_web/src/assets/aossie_logo_transparent.png new file mode 100644 index 0000000..deb3749 Binary files /dev/null and b/eduaid_web/src/assets/aossie_logo_transparent.png differ diff --git a/eduaid_web/src/pages/Output.jsx b/eduaid_web/src/pages/Output.jsx index d494a72..28d91cd 100644 --- a/eduaid_web/src/pages/Output.jsx +++ b/eduaid_web/src/pages/Output.jsx @@ -1,13 +1,29 @@ import React, { useState, useEffect } from "react"; -import { PDFDocument } from "pdf-lib"; +import { PDFDocument, rgb } from "pdf-lib"; import "../index.css"; import logo from "../assets/aossie_logo.png"; +import logoPNG from "../assets/aossie_logo_transparent.png"; + const Output = () => { const [qaPairs, setQaPairs] = useState([]); const [questionType, setQuestionType] = useState( localStorage.getItem("selectedQuestionType") ); + const [pdfMode, setPdfMode] = useState("questions"); + + useEffect(() => { + const handleClickOutside = (event) => { + const dropdown = document.getElementById('pdfDropdown'); + if (dropdown && !dropdown.contains(event.target) && + !event.target.closest('button')) { + dropdown.classList.add('hidden'); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); +}, []); function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { @@ -47,31 +63,14 @@ const Output = () => { }); } - if (qaPairsFromStorage["output_shortq"]) { - qaPairsFromStorage["output_shortq"]["questions"].forEach((qaPair) => { - combinedQaPairs.push({ - question: qaPair.Question, - question_type: "Short", - answer: qaPair.Answer, - context: qaPair.context, - }); - }); - } - - if (questionType === "get_mcq") { + if (qaPairsFromStorage["output_mcq"] || questionType === "get_mcq") { qaPairsFromStorage["output"].forEach((qaPair) => { - const options = qaPair.answer - .filter((ans) => !ans.correct) - .map((ans) => ans.answer); - const correctAnswer = qaPair.answer.find( - (ans) => ans.correct - )?.answer; - combinedQaPairs.push({ - question: qaPair.question, - question_type: "MCQ_Hard", - options: options, - answer: correctAnswer, + question: qaPair.question_statement, + question_type: "MCQ", + options: qaPair.options, + answer: qaPair.answer, + context: qaPair.context, }); }); } @@ -121,104 +120,228 @@ const Output = () => { } }; - const generatePDF = async () => { + const loadLogoAsBytes = async () => { + try { + const response = await fetch(logoPNG); + const arrayBuffer = await response.arrayBuffer(); + return new Uint8Array(arrayBuffer); + } catch (error) { + console.error('Error loading logo:', error); + return null; + } + }; + + const generatePDF = async (mode) => { + const pageWidth = 595.28; + const pageHeight = 841.89; + const margin = 50; + const maxContentWidth = pageWidth - (2 * margin); + const maxContentHeight = pageHeight - (2 * margin); + const pdfDoc = await PDFDocument.create(); - let page = pdfDoc.addPage(); + let page = pdfDoc.addPage([pageWidth, pageHeight]); const d = new Date(Date.now()); - page.drawText("EduAid generated Quiz", { x: 50, y: 800, size: 20 }); - page.drawText("Created On: " + d.toString(), { x: 50, y: 770, size: 10 }); + + // Load and embed logo + const logoBytes = await loadLogoAsBytes(); + let logoImage; + if (logoBytes) { + try { + logoImage = await pdfDoc.embedPng(logoBytes); + const logoDims = logoImage.scale(0.2); // Scale down the logo + page.drawImage(logoImage, { + x: margin, + y: pageHeight - margin - 30, + width: logoDims.width, + height: logoDims.height, + }); + // Adjust title position to be next to the logo + page.drawText('EduAid generated Quiz', { + x: margin + logoDims.width + 10, + y: pageHeight - margin, + size: 20 + }); + page.drawText('Created On: ' + d.toString(), { + x: margin + logoDims.width + 10, + y: pageHeight - margin - 30, + size: 10 + }); + } catch (error) { + console.error('Error embedding logo:', error); + // Fallback to text-only header if logo embedding fails + page.drawText('EduAid generated Quiz', { + x: margin, + y: pageHeight - margin, + size: 20 + }); + page.drawText('Created On: ' + d.toString(), { + x: margin, + y: pageHeight - margin - 30, + size: 10 + }); + } + } + + const form = pdfDoc.getForm(); - let y = 700; // Starting y position for content + let y = pageHeight - margin - 70; let questionIndex = 1; - qaPairs.forEach((qaPair) => { - if (y < 50) { - page = pdfDoc.addPage(); - y = 700; - } + const createNewPageIfNeeded = (requiredHeight) => { + if (y - requiredHeight < margin) { + page = pdfDoc.addPage([pageWidth, pageHeight]); + y = pageHeight - margin; + return true; + } + return false; + }; - page.drawText(`Q${questionIndex}) ${qaPair.question}`, { - x: 50, - y, - size: 15, + const wrapText = (text, maxWidth) => { + const words = text.split(' '); + const lines = []; + let currentLine = ''; + + words.forEach(word => { + const testLine = currentLine ? `${currentLine} ${word}` : word; + + // Adjust the multiplier to reflect a more realistic line width based on font size + const testWidth = testLine.length * 6; // Update the multiplier for better wrapping. + + if (testWidth > maxWidth) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine = testLine; + } }); - y -= 30; + + if (currentLine) { + lines.push(currentLine); + } + + return lines; + }; + - if (qaPair.question_type === "Boolean") { - // Create radio buttons for True/False - const radioGroup = form.createRadioGroup( - `question${questionIndex}_answer` - ); - const drawRadioButton = (text, selected) => { - const options = { - x: 70, - y, - width: 15, - height: 15, - }; + qaPairs.forEach((qaPair) => { + let requiredHeight = 60; + const questionLines = wrapText(qaPair.question, maxContentWidth); + requiredHeight += questionLines.length * 20; - radioGroup.addOptionToPage(text, page, options); - page.drawText(text, { x: 90, y: y + 2, size: 12 }); - y -= 20; - }; + if (mode !== 'answers') { + if (qaPair.question_type === "Boolean") { + requiredHeight += 60; + } else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") { + const optionsCount = qaPair.options ? qaPair.options.length + 1 : 1; + requiredHeight += optionsCount * 25; + } else { + requiredHeight += 40; + } + } - drawRadioButton("True", false); - drawRadioButton("False", false); - } else if ( - qaPair.question_type === "MCQ" || - qaPair.question_type === "MCQ_Hard" - ) { - // Shuffle options including qaPair.answer - const options = [...qaPair.options, qaPair.answer]; // Include correct answer in options - options.sort(() => Math.random() - 0.5); // Shuffle options randomly + if (mode === 'answers' || mode === 'questions_answers') { + requiredHeight += 40; + } - const radioGroup = form.createRadioGroup( - `question${questionIndex}_answer` - ); + createNewPageIfNeeded(requiredHeight); - options.forEach((option, index) => { - const drawRadioButton = (text, selected) => { - const radioOptions = { - x: 70, - y, - width: 15, - height: 15, - }; - radioGroup.addOptionToPage(text, page, radioOptions); - page.drawText(text, { x: 90, y: y + 2, size: 12 }); - y -= 20; - }; - drawRadioButton(option, false); - }); - } else if (qaPair.question_type === "Short") { - // Text field for Short answer - const answerField = form.createTextField( - `question${questionIndex}_answer` - ); - answerField.setText(""); - answerField.addToPage(page, { - x: 50, - y: y - 20, - width: 450, - height: 20, - }); - y -= 40; - } + if (mode !== 'answers') { + questionLines.forEach((line, lineIndex) => { + const textToDraw = lineIndex === 0 + ? `Q${questionIndex}) ${line}` // First line includes question number + : ` ${line}`; // Subsequent lines are indented + page.drawText(textToDraw, { + x: margin, + y: y - (lineIndex * 20), + size: 12, + maxWidth: maxContentWidth + }); + }); + y -= (questionLines.length * 20 + 20); + + if (mode === 'questions') { + if (qaPair.question_type === "Boolean") { + const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); + ['True', 'False'].forEach((option) => { + const radioOptions = { + x: margin + 20, + y, + width: 15, + height: 15, + }; + radioGroup.addOptionToPage(option, page, radioOptions); + page.drawText(option, { x: margin + 40, y: y + 2, size: 12 }); + y -= 20; + }); + } else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") { + const allOptions = [...(qaPair.options || [])]; + if (qaPair.answer && !allOptions.includes(qaPair.answer)) { + allOptions.push(qaPair.answer); + } + const shuffledOptions = shuffleArray([...allOptions]); + + const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); + shuffledOptions.forEach((option, index) => { + const radioOptions = { + x: margin + 20, + y, + width: 15, + height: 15, + }; + radioGroup.addOptionToPage(`option${index}`, page, radioOptions); + const optionLines = wrapText(option, maxContentWidth - 60); + optionLines.forEach((line, lineIndex) => { + page.drawText(line, { + x: margin + 40, + y: y + 2 - (lineIndex * 15), + size: 12 + }); + }); + y -= Math.max(25, optionLines.length * 20); + }); + } else if (qaPair.question_type === "Short") { + const answerField = form.createTextField(`question${questionIndex}_answer`); + answerField.setText(""); + answerField.addToPage(page, { + x: margin, + y: y - 20, + width: maxContentWidth, + height: 20 + }); + y -= 40; + } + } + } + + if (mode === 'answers' || mode === 'questions_answers') { + const answerText = `Answer ${questionIndex}: ${qaPair.answer}`; + const answerLines = wrapText(answerText, maxContentWidth); + answerLines.forEach((line, lineIndex) => { + page.drawText(line, { + x: margin, + y: y - (lineIndex * 15), + size: 12, + color: rgb(0, 0.5, 0) + }); + }); + y -= answerLines.length * 20; + } - y -= 20; // Space between questions - questionIndex += 1; + y -= 20; + questionIndex += 1; }); - // Save PDF and create download link const pdfBytes = await pdfDoc.save(); - const blob = new Blob([pdfBytes], { type: "application/pdf" }); - const link = document.createElement("a"); + const blob = new Blob([pdfBytes], { type: 'application/pdf' }); + const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = "generated_questions.pdf"; document.body.appendChild(link); link.click(); document.body.removeChild(link); - }; + + document.getElementById('pdfDropdown').classList.add('hidden'); +}; return (
@@ -291,12 +414,37 @@ const Output = () => { > Generate Google form - +
+ +
+ + + +
+
diff --git a/extension/src/assets/aossie_logo.png b/extension/src/assets/aossie_logo.png new file mode 100644 index 0000000..deb3749 Binary files /dev/null and b/extension/src/assets/aossie_logo.png differ diff --git a/extension/src/pages/question/Question.jsx b/extension/src/pages/question/Question.jsx index d988471..1234d37 100644 --- a/extension/src/pages/question/Question.jsx +++ b/extension/src/pages/question/Question.jsx @@ -1,14 +1,29 @@ import React, { useState, useEffect } from "react"; import ReactDOM from "react-dom"; -import { PDFDocument } from 'pdf-lib'; +import { PDFDocument, rgb } from 'pdf-lib'; import "../../index.css"; import logo from "../../assets/aossie_logo.webp"; +import logoPNG from "../../assets/aossie_logo.png"; function Question() { const [qaPairs, setQaPairs] = useState([]); const [questionType, setQuestionType] = useState( localStorage.getItem("selectedQuestionType") ); + const [pdfMode, setPdfMode] = useState("questions"); + + useEffect(() => { + const handleClickOutside = (event) => { + const dropdown = document.getElementById('pdfDropdown'); + if (dropdown && !dropdown.contains(event.target) && + !event.target.closest('button')) { + dropdown.classList.add('hidden'); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => document.removeEventListener('mousedown', handleClickOutside); +}, []); function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { @@ -48,31 +63,14 @@ function Question() { }); } - if (qaPairsFromStorage["output_shortq"]) { - qaPairsFromStorage["output_shortq"]["questions"].forEach((qaPair) => { - combinedQaPairs.push({ - question: qaPair.Question, - question_type: "Short", - answer: qaPair.Answer, - context: qaPair.context, - }); - }); - } - - if (questionType === "get_mcq") { + if (qaPairsFromStorage["output_mcq"] || questionType === "get_mcq") { qaPairsFromStorage["output"].forEach((qaPair) => { - const options = qaPair.answer - .filter((ans) => !ans.correct) - .map((ans) => ans.answer); - const correctAnswer = qaPair.answer.find( - (ans) => ans.correct - )?.answer; - combinedQaPairs.push({ - question: qaPair.question, - question_type: "MCQ_Hard", - options: options, - answer: correctAnswer, + question: qaPair.question_statement, + question_type: "MCQ", + options: qaPair.options, + answer: qaPair.answer, + context: qaPair.context, }); }); } @@ -122,77 +120,217 @@ function Question() { } }; - const generatePDF = async () => { + const loadLogoAsBytes = async () => { + try { + const response = await fetch(logoPNG); + const arrayBuffer = await response.arrayBuffer(); + return new Uint8Array(arrayBuffer); + } catch (error) { + console.error('Error loading logo:', error); + return null; + } + }; + + const generatePDF = async (mode) => { + const pageWidth = 595.28; + const pageHeight = 841.89; + const margin = 50; + const maxContentWidth = pageWidth - (2 * margin); + const maxContentHeight = pageHeight - (2 * margin); + const pdfDoc = await PDFDocument.create(); - let page = pdfDoc.addPage(); + let page = pdfDoc.addPage([pageWidth, pageHeight]); const d = new Date(Date.now()); - page.drawText('EduAid generated Quiz', { x: 50, y: 800, size: 20 }); - page.drawText('Created On: ' + d.toString(), { x: 50, y: 770, size: 10 }); + + // Load and embed logo + const logoBytes = await loadLogoAsBytes(); + let logoImage; + if (logoBytes) { + try { + logoImage = await pdfDoc.embedPng(logoBytes); + const logoDims = logoImage.scale(0.2); // Scale down the logo + page.drawImage(logoImage, { + x: margin, + y: pageHeight - margin - 30, + width: logoDims.width, + height: logoDims.height, + }); + // Adjust title position to be next to the logo + page.drawText('EduAid generated Quiz', { + x: margin + logoDims.width + 10, + y: pageHeight - margin, + size: 20 + }); + page.drawText('Created On: ' + d.toString(), { + x: margin + logoDims.width + 10, + y: pageHeight - margin - 30, + size: 10 + }); + } catch (error) { + console.error('Error embedding logo:', error); + // Fallback to text-only header if logo embedding fails + page.drawText('EduAid generated Quiz', { + x: margin, + y: pageHeight - margin, + size: 20 + }); + page.drawText('Created On: ' + d.toString(), { + x: margin, + y: pageHeight - margin - 30, + size: 10 + }); + } + } + + const form = pdfDoc.getForm(); - let y = 700; // Starting y position for content + let y = pageHeight - margin - 70; let questionIndex = 1; - qaPairs.forEach((qaPair) => { - if (y < 50) { - page = pdfDoc.addPage(); - y = 700; + const createNewPageIfNeeded = (requiredHeight) => { + if (y - requiredHeight < margin) { + page = pdfDoc.addPage([pageWidth, pageHeight]); + y = pageHeight - margin; + return true; } + return false; + }; + + const wrapText = (text, maxWidth) => { + const words = text.split(' '); + const lines = []; + let currentLine = ''; + + words.forEach(word => { + const testLine = currentLine ? `${currentLine} ${word}` : word; + + // Adjust the multiplier to reflect a more realistic line width based on font size + const testWidth = testLine.length * 6; // Update the multiplier for better wrapping. + + if (testWidth > maxWidth) { + lines.push(currentLine); + currentLine = word; + } else { + currentLine = testLine; + } + }); + + if (currentLine) { + lines.push(currentLine); + } + + return lines; + }; + + + qaPairs.forEach((qaPair) => { + let requiredHeight = 60; + const questionLines = wrapText(qaPair.question, maxContentWidth); + requiredHeight += questionLines.length * 20; - page.drawText(`Q${questionIndex}) ${qaPair.question}`, { x: 50, y, size: 15 }); - y -= 30; + if (mode !== 'answers') { + if (qaPair.question_type === "Boolean") { + requiredHeight += 60; + } else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") { + const optionsCount = qaPair.options ? qaPair.options.length + 1 : 1; + requiredHeight += optionsCount * 25; + } else { + requiredHeight += 40; + } + } - if (qaPair.question_type === "Boolean") { - // Create radio buttons for True/False - const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); - const drawRadioButton = (text, selected) => { - const options = { - x: 70, - y, - width: 15, - height: 15, - }; + if (mode === 'answers' || mode === 'questions_answers') { + requiredHeight += 40; + } - radioGroup.addOptionToPage(text, page, options); - page.drawText(text, { x: 90, y: y + 2, size: 12 }); - y -= 20; - }; + createNewPageIfNeeded(requiredHeight); - drawRadioButton('True', false); - drawRadioButton('False', false); - } else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") { - // Shuffle options including qaPair.answer - const options = [...qaPair.options, qaPair.answer]; // Include correct answer in options - options.sort(() => Math.random() - 0.5); // Shuffle options randomly + if (mode !== 'answers') { + questionLines.forEach((line, lineIndex) => { + const textToDraw = lineIndex === 0 + ? `Q${questionIndex}) ${line}` // First line includes question number + : ` ${line}`; // Subsequent lines are indented + page.drawText(textToDraw, { + x: margin, + y: y - (lineIndex * 20), + size: 12, + maxWidth: maxContentWidth + }); + }); + y -= (questionLines.length * 20 + 20); - const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); + if (mode === 'questions') { + if (qaPair.question_type === "Boolean") { + const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); + ['True', 'False'].forEach((option) => { + const radioOptions = { + x: margin + 20, + y, + width: 15, + height: 15, + }; + radioGroup.addOptionToPage(option, page, radioOptions); + page.drawText(option, { x: margin + 40, y: y + 2, size: 12 }); + y -= 20; + }); + } else if (qaPair.question_type === "MCQ" || qaPair.question_type === "MCQ_Hard") { + const allOptions = [...(qaPair.options || [])]; + if (qaPair.answer && !allOptions.includes(qaPair.answer)) { + allOptions.push(qaPair.answer); + } + const shuffledOptions = shuffleArray([...allOptions]); + + const radioGroup = form.createRadioGroup(`question${questionIndex}_answer`); + shuffledOptions.forEach((option, index) => { + const radioOptions = { + x: margin + 20, + y, + width: 15, + height: 15, + }; + radioGroup.addOptionToPage(`option${index}`, page, radioOptions); + const optionLines = wrapText(option, maxContentWidth - 60); + optionLines.forEach((line, lineIndex) => { + page.drawText(line, { + x: margin + 40, + y: y + 2 - (lineIndex * 15), + size: 12 + }); + }); + y -= Math.max(25, optionLines.length * 20); + }); + } else if (qaPair.question_type === "Short") { + const answerField = form.createTextField(`question${questionIndex}_answer`); + answerField.setText(""); + answerField.addToPage(page, { + x: margin, + y: y - 20, + width: maxContentWidth, + height: 20 + }); + y -= 40; + } + } + } - options.forEach((option, index) => { - const drawRadioButton = (text, selected) => { - const radioOptions = { - x: 70, - y, - width: 15, - height: 15, - }; - radioGroup.addOptionToPage(text, page, radioOptions); - page.drawText(text, { x: 90, y: y + 2, size: 12 }); - y -= 20; - }; - drawRadioButton(option, false); + if (mode === 'answers' || mode === 'questions_answers') { + const answerText = `Answer ${questionIndex}: ${qaPair.answer}`; + const answerLines = wrapText(answerText, maxContentWidth); + answerLines.forEach((line, lineIndex) => { + page.drawText(line, { + x: margin, + y: y - (lineIndex * 15), + size: 12, + color: rgb(0, 0.5, 0) + }); }); - } else if (qaPair.question_type === "Short") { - // Text field for Short answer - const answerField = form.createTextField(`question${questionIndex}_answer`); - answerField.setText(""); - answerField.addToPage(page, { x: 50, y: y - 20, width: 450, height: 20 }); - y -= 40; + y -= answerLines.length * 20; } - y -= 20; // Space between questions + y -= 20; questionIndex += 1; }); - // Save PDF and create download link const pdfBytes = await pdfDoc.save(); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); const link = document.createElement('a'); @@ -201,6 +339,8 @@ function Question() { document.body.appendChild(link); link.click(); document.body.removeChild(link); + + document.getElementById('pdfDropdown').classList.add('hidden'); }; @@ -282,12 +422,38 @@ function Question() { > Generate Google form - + +
+ +
+ + + +
+
@@ -295,4 +461,4 @@ function Question() { ); } -ReactDOM.render(, document.getElementById("root")); +ReactDOM.render(, document.getElementById("root")); \ No newline at end of file