1
Fork 0

Merge branch 'sort-by-year' into 'main'

Add Sorting Functionality and UI Enhancements

See merge request ibdocs.2/pestlev3!2
This commit is contained in:
Advay Chandorkar 2025-04-02 03:01:27 +00:00
commit 50127311d4
3 changed files with 210 additions and 113 deletions

View file

@ -29,6 +29,11 @@
<div id="root">
<header>
<div style="position: fixed; right: 15px; z-index: 9999;">
<span class="sort-controls" style="margin-right: 10px;">
<label style="margin-right: 10px;">Sort by:</label>
<button id="sort-by-year" class="btn-secondary" onclick="handleYearSort()">Year ▼</button>
<button id="reset-sorting" class="btn-secondary" onclick="resetSorting()">Reset Order</button>
</span>
<button id="addalltoPDFbtn" class="btn-primary hidden" onclick="addalltoPDF()">Add all Qs to PDF</button>
<button id="generatePDFbtn" class="btn-primary hidden" onclick="generatePDF()">Generate PDF!</button>
<button id="darkmodebtn" class="btn-primary" onclick="toggleDarkMode()">Dark Mode</button>

View file

@ -5,6 +5,16 @@ document.addEventListener('DOMContentLoaded', () => {
jsonDataFetched = false;
});
function handleYearSort() {
const yearButton = document.getElementById('sort-by-year');
if (yearButton.textContent === 'Year ▼') {
sortQuestionsByYear('asc');
yearButton.textContent = 'Year ▲';
} else {
sortQuestionsByYear('desc');
yearButton.textContent = 'Year ▼';
}
}
function toggleModal(){document.getElementById("modal").classList.toggle("hidden")}
function toggleMS(){document.getElementById("markscheme").classList.toggle("hidden")}
@ -12,7 +22,6 @@ function toggleR(){document.getElementById("report").classList.toggle("hidden")}
function toggleHelp(){document.getElementById("helpmenu").classList.toggle("hidden")}
function toggleDownAllQs(){document.getElementById("addalltoPDFbtn").classList.remove('hidden');document.getElementById("generatePDFbtn").classList.remove('hidden')}
function toggleDarkMode() {
var body = document.body;
var head = document.head;
@ -152,6 +161,8 @@ let jsonDataFetched = false;
let jsonData = null;
let currentFileName = null;
let topics = [];
let subtopics = [];
let sortOrder = null;
const domCache = {
rightCol: document.getElementById("right-col"),
@ -312,16 +323,85 @@ async function loadJSON(filename) {
}
}
// Extract year from question ID (e.g., "23M.1A.SL.TZ2.39" -> 2023)
function extractYear(questionId) {
if (!questionId || typeof questionId !== 'string') return 0;
// Extract the first two digits
const match = questionId.match(/^(\d{2})/);
if (!match) return 0;
function processData(data, filename) {
jsonDataFetched = true;
currentFileName = filename;
let year = parseInt(match[1], 10);
topics = [...new Set(data.flatMap(item => item.topics))].sort();
subtopics = [...new Set(data.flatMap(item => item.subtopics))].sort();
renderTopics();
// Convert to four-digit year
if (year >= 0 && year <= 99) {
if (year >= 0 && year <= 25) {
year += 2000; // 00-25 -> 2000-2025
} else {
year += 1900; // 26-99 -> 1926-1999
}
}
return year;
}
// Function to sort questions by year
function sortQuestionsByYear(direction = 'asc') {
if (!jsonData || !Array.isArray(jsonData)) return;
sortOrder = direction;
const sortedData = [...jsonData].sort((a, b) => {
const yearA = extractYear(a.question_id);
const yearB = extractYear(b.question_id);
return direction === 'asc' ? yearA - yearB : yearB - yearA;
});
// Clear the existing questions
clearDisplayedQuestions();
// Re-render with sorted data
renderQuestions(sortedData);
// Maintain topic filter states
document.querySelectorAll('input[name="topic"]:checked').forEach(checkbox => {
document.querySelectorAll(`div[class*="${checkbox.value}"]`)
.forEach(div => div.classList.remove('hidden'));
});
}
// Function to reset sorting and show questions in original order
function resetSorting() {
if (!jsonData || !Array.isArray(jsonData)) return;
sortOrder = null;
document.getElementById('sort-by-year').textContent = 'Year ▼';
// Clear the existing questions
clearDisplayedQuestions();
// Re-render with original data
renderQuestions(jsonData);
// Maintain topic filter states
document.querySelectorAll('input[name="topic"]:checked').forEach(checkbox => {
document.querySelectorAll(`div[class*="${checkbox.value}"]`)
.forEach(div => div.classList.remove('hidden'));
});
}
// Function to clear displayed questions but keep filters
function clearDisplayedQuestions() {
domCache.rightCol.innerHTML = '';
document.querySelectorAll('#markscheme-box > div').forEach(el => el.remove());
document.querySelectorAll('#report-box > div').forEach(el => el.remove());
document.querySelectorAll('#markscheme-box2 > svg').forEach(el => el.remove());
document.querySelectorAll('#report-box2 > svg').forEach(el => el.remove());
}
// Function to render questions (extracted from processData)
function renderQuestions(data) {
const fragment = document.createDocumentFragment();
data.forEach(item => {
@ -331,17 +411,13 @@ function processData(data, filename) {
Markscheme: markscheme,
'Examiners report': report,
topics,
subtopics
subtopics,
year
} = item;
const bigQuestionBox = document.createElement("div");
bigQuestionBox.id = questionid;
/*const allClasses = [...topics.map(t => t.trim()),
subtopics,
"hidden"];
bigQuestionBox.classList.add(...allClasses);*/
const allClasses = [
...topics.map(t => t.trim()).filter(t => t),
...(typeof subtopics === "string" ? [subtopics] : []),
@ -350,12 +426,12 @@ function processData(data, filename) {
bigQuestionBox.classList.add(...allClasses);
const btnContainer = document.createElement("div");
btnContainer.classList.add("btn-container");
function toggleMScont(questionid) {
const markschemeContainer = document.getElementById(`markscheme-${questionid} ${currentFileName}`);
const toggleMSSvg = document.querySelector(`#markscheme-box2 svg`); // Simplified selector
toggleMSSvg.classList.toggle('hidden');
markschemeContainer.classList.toggle('hidden');
activeQuestionId = markschemeContainer.classList.contains('hidden') ? null : questionid;
@ -363,6 +439,7 @@ function processData(data, filename) {
function toggleRepcont(questionid) {
const reportContainer = document.getElementById(`report-${questionid} ${currentFileName}`);
const toggleRepSvg = document.querySelector(`#report-box2 svg`); // Simplified selector
toggleRepSvg.classList.toggle('hidden');
reportContainer.classList.toggle('hidden');
activeQuestionId = reportContainer.classList.contains('hidden') ? null : questionid;
@ -376,8 +453,13 @@ function processData(data, filename) {
buttons.forEach(button => btnContainer.appendChild(button));
// Add full year from extracted two-digit year
const extractedYear = extractYear(questionid);
const yearText = extractedYear ? `<h4><b>Year:</b> ${extractedYear}</h4>` : '';
const content = `
<h3>${questionid}</h3>
${yearText}
<h4><b>Topics:</b> ${topics.join(', ')}</h4>
<h4><b>Subtopics:</b> ${subtopics.join(', ')}</h4>
<div class="square-container">${question}</div>
@ -387,30 +469,19 @@ function processData(data, filename) {
bigQuestionBox.querySelector('h3').after(btnContainer);
if (markscheme) {
createContainer('markscheme', questionid, filename, markscheme, domCache.msbox);
createContainer('markscheme', questionid, currentFileName, markscheme, domCache.msbox);
}
if (report) {
createContainer('report', questionid, filename, report, domCache.reportbox);
createContainer('report', questionid, currentFileName, report, domCache.reportbox);
}
/********** Removed ID appending method since it was slowing down interaction with the X svg *********/
const toggleMSSvg = createSVGElement(questionid);
//toggleMSSvg.id = `toggleMSSvg-${questionid}`;
const toggleRepSvg = createSVGElement(questionid);
//toggleRepSvg.id = `toggleRepSvg-${questionid}`;
domCache.msbox2.appendChild(toggleMSSvg);
domCache.repbox2.appendChild(toggleRepSvg);
/*toggleMSSvg.addEventListener('click', () => {
toggleMScont(questionid);
toggleMS();
});*/
/********** Identifying by active question instead **********/
let activeQuestionId = null;
const handleToggle = () => {
@ -423,7 +494,6 @@ function processData(data, filename) {
toggleMS();
markschemeContainer.classList.toggle('hidden');
activeQuestionId = null;
} else if (reportContainer && !reportContainer.classList.contains('hidden')) {
toggleRepSvg.classList.toggle('hidden');
toggleR();
@ -431,21 +501,10 @@ function processData(data, filename) {
activeQuestionId = null;
}
}
}
};
toggleMSSvg.addEventListener('click', handleToggle);
toggleRepSvg.addEventListener('click', handleToggle);
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
handleToggle();
}
});
/*toggleRepSvg.addEventListener('click', () => {
toggleRepcont(questionid);
toggleR();
});*/
fragment.appendChild(bigQuestionBox);
});
@ -453,30 +512,26 @@ function processData(data, filename) {
domCache.rightCol.appendChild(fragment);
updateSquareContainers();
toggleDownAllQs();
}
/******** Old function for fetching JSON, deprecated in favor of IndexedDB ********/
/*function loadJSON(filename) {
fetch(`https://pub-59370068cd854c158959e7ca4578e5bd.r2.dev/${filename}`) // ../assets/jsonqb/
.then(response => response.json())
.then(data => {
function processData(data, filename) {
jsonData = data; // Store the data for sorting
jsonDataFetched = true;
currentFileName = filename;
topics = [...new Set(data.flatMap(item => item.topics))].sort();
subtopics = [...new Set(data.flatMap(item => item.subtopics))].sort();
renderTopics();
const fragment = document.createDocumentFragment();
// Reset sort button state
const yearButton = document.getElementById('sort-by-year');
if (yearButton) {
yearButton.textContent = 'Year ▼';
}
data.forEach(item => {});
domCache.rightCol.appendChild(fragment);
updateSquareContainers();
})
.catch(error => console.error('Error fetching JSON:', error));
}*/
// Render questions
renderQuestions(data);
}
function createButton({ text, handler, className = 'btn-secondary' }) {
const button = document.createElement("button");
@ -552,9 +607,6 @@ document.addEventListener('DOMContentLoaded', () => {
if (jsonDataFetched && filename !== currentFileName) {
resetState();
loadJSON(filename);
//} else if (jsonDataFetched && filename === currentFileName) {
//resetState();
//jsonDataFetched = false;
} else if (!jsonDataFetched) {
loadJSON(filename);
}
@ -564,5 +616,28 @@ document.addEventListener('DOMContentLoaded', () => {
function resetState() {
domCache.rightCol.innerHTML = '';
document.querySelectorAll('.topic-label').forEach(label => label.remove());
sessionStorage.setItem('selectedQuestionIds', '[]');
const yearButton = document.getElementById('sort-by-year');
if (yearButton) {
yearButton.textContent = 'Year ▼';
}
sessionStorage.setItem('selectedQuestionIds', '[]');
sortOrder = null;
}
function toggleDarkMode() {
var body = document.body;
var head = document.head;
var toggleButton = document.getElementById("darkmodebtn");
if (localStorage.getItem("darkMode") === "disabled") {
body.classList.add("dark-mode");
head.classList.add("dark-mode");
localStorage.setItem("darkMode", "enabled");
toggleButton.innerText = "Light Mode";
} else {
body.classList.remove("dark-mode");
head.classList.remove("dark-mode");
localStorage.setItem("darkMode", "disabled");
toggleButton.innerText = "Dark Mode";
}
}

View file

@ -356,6 +356,23 @@ h4 {
overflow-y: scroll;
}
#sorting-container {
margin-bottom: 15px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 5px;
border: 1px solid #ddd;
}
#sorting-container label {
font-weight: bold;
margin-right: 10px;
}
#sorting-container button {
margin-right: 5px;
}
.square-container {
display: flex;
flex-direction: column;