1
Fork 0

Update 6 files

- /app/index.html
- /app/index.js
- /assets/style.css
- /assets/jsonqb/Chemistry 2025 QB merged.json
- /assets/jsonqb/Physics 2025 QB merged.json
- /assets/jsonqb/Biology 2025 QB merged.json
This commit is contained in:
pirateIB 2025-06-20 11:29:08 +00:00
parent 7db98ac97c
commit 70f62f995e
6 changed files with 7822 additions and 30 deletions

View file

@ -27,14 +27,6 @@
Loading, please wait... Loading, please wait...
</div> </div>
<div id="root"> <div id="root">
<header>
<div style="position: fixed; right: 15px; z-index: 9999;">
<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>
<button id="helpbtn" class="btn-secondary" onclick="toggleHelp()">Help</button>
</div>
</header>
<div id="appContainer" class="h-full w-full"> <div id="appContainer" class="h-full w-full">
<div id="left-col" class="flex flex-col bg-white p-2"> <div id="left-col" class="flex flex-col bg-white p-2">
<div class="p-3"><a href="../index.html"><button class="btn-primary">< Go Home</button></button></a></div> <div class="p-3"><a href="../index.html"><button class="btn-primary">< Go Home</button></button></a></div>
@ -55,7 +47,50 @@
<br> <br>
<div id="topic-list"></div> <div id="topic-list"></div>
</div> </div>
<div id="right-col" class="flex flex-col bg-gray-100 p-2"> <div id="right-wrapper">
<div id="upper-right-col" class="flex flex-col bg-gray-100 p-2">
<div class="toolbar">
<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>
<button id="helpbtn" class="btn-secondary" onclick="toggleHelp()">Help</button>
</div>
<div class="selectables p-2 hidden">
<label for="paper-select"><b>Paper:</b>
<select id="paper-select">
<option value="">All</option>
<option value="1">Paper 1</option>
<option value="2">Paper 2</option>
<option value="3">Paper 3</option>
</select>
</label>
<label for="level-select"><b>Level:</b>
<select id="level-select">
<option value="">All</option>
<option value="standard">SL</option>
<option value="higher">HL</option>
</select>
</label>
<!--<label for="topic-select"><b>Topics:</b>
<select id="topic-select">
<option value="">All</option>
</select>
</label>-->
<label for="subtopic-select" ><b>Subtopics:</b>
<select id="subtopic-select">
<option value="">All</option>
</select>
</label>
</div>
</div>
<div id="right-col" class="flex flex-col bg-gray-100 p-2"></div>
</div> </div>
<div id="modal" class="fixed left-0 top-0 hidden flex h-full w-full items-center justify-center bg-black/70"> <div id="modal" class="fixed left-0 top-0 hidden flex h-full w-full items-center justify-center bg-black/70">
<div class="flex h-full w-full md:h-3/4 md:w-3/4 flex-col border bg-gray-100 p-4"> <div class="flex h-full w-full md:h-3/4 md:w-3/4 flex-col border bg-gray-100 p-4">
@ -98,6 +133,19 @@
<h4 class="ms-2">IB Biology 2025 QuestionBank (split by parts) <h4 class="ms-2">IB Biology 2025 QuestionBank (split by parts)
</h4> </h4>
</div> </div>
<div class="flex items-center mt-2">
<button id="bioqb25-merged" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="me-2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="16"></line>
<line x1="8" y1="12" x2="16" y2="12"></line>
</svg> Add
</button>
<h4 class="ms-2">IB Biology 2025 QuestionBank (parts merged)
</h4>
</div>
<div class="flex items-center mt-2"> <div class="flex items-center mt-2">
<button id="bmqb" class="btn-primary flex items-center" onclick="toggleModal()"> <button id="bmqb" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
@ -137,6 +185,19 @@
<h4 class="ms-2">IB Chemistry 2025 QuestionBank (split by parts) <h4 class="ms-2">IB Chemistry 2025 QuestionBank (split by parts)
</h4> </h4>
</div> </div>
<div class="flex items-center mt-2">
<button id="chemqb25-merged" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="me-2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="16"></line>
<line x1="8" y1="12" x2="16" y2="12"></line>
</svg> Add
</button>
<h4 class="ms-2">IB Chemistry 2025 QuestionBank (parts merged)
</h4>
</div>
<div class="flex items-center mt-2"> <div class="flex items-center mt-2">
<button id="compsciqb" class="btn-primary flex items-center" onclick="toggleModal()"> <button id="compsciqb" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
@ -280,6 +341,19 @@
<h4 class="ms-2">IB Physics 2025 QuestionBank (split by parts) <h4 class="ms-2">IB Physics 2025 QuestionBank (split by parts)
</h4> </h4>
</div> </div>
<div class="flex items-center mt-2">
<button id="phyqb25-merged" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="me-2">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="16"></line>
<line x1="8" y1="12" x2="16" y2="12"></line>
</svg> Add
</button>
<h4 class="ms-2">IB Physics 2025 QuestionBank (parts merged)
</h4>
</div>
<div class="flex items-center mt-2"> <div class="flex items-center mt-2">
<button id="psychqb" class="btn-primary flex items-center" onclick="toggleModal()"> <button id="psychqb" class="btn-primary flex items-center" onclick="toggleModal()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
@ -325,9 +399,9 @@
</div> </div>
<div class="m-0 mt-2 overflow-auto p-0 font-serif max-h-60vh"> <div class="m-0 mt-2 overflow-auto p-0 font-serif max-h-60vh">
<div class="font-sans"> <div class="font-sans">
<h3>· Why are QBv6 (2025 sciences) questions split by parts?</h3> <h3>· The 2025 science questionbanks have very few questions. Will you merge the previous questionbank into it?</h3>
<br> <br>
<p>Ask IB ¯\_(ツ)_/¯ Full combined version coming soon!</p> <p>Soon™... it requires huge manual work because the syllabus changes are big.</p>
<br> <br>
<h3>· I tried to save all the questions as PDF, and my computer got frozen!</h3> <h3>· I tried to save all the questions as PDF, and my computer got frozen!</h3>
<br> <br>

View file

@ -11,6 +11,21 @@ function toggleMS(){document.getElementById("markscheme").classList.toggle("hidd
function toggleR(){document.getElementById("report").classList.toggle("hidden")} function toggleR(){document.getElementById("report").classList.toggle("hidden")}
function toggleHelp(){document.getElementById("helpmenu").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 toggleDownAllQs(){document.getElementById("addalltoPDFbtn").classList.remove('hidden');document.getElementById("generatePDFbtn").classList.remove('hidden')}
function toggleFilters(){document.querySelector(".selectables").classList.remove('hidden')};
function checkWidth() {
const btn1 = document.getElementById('generatePDFbtn');
const btn2 = document.getElementById('addalltoPDFbtn');
if (window.innerWidth <= 480) {
btn1.style.display = 'none';
btn2.style.display = 'none';
} else {
btn1.style.display = '';
btn2.style.display = '';
}
}
window.addEventListener('resize', checkWidth);
function toggleDarkMode() { function toggleDarkMode() {
@ -56,7 +71,7 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
function startRandomTimerLoop() { /*function startRandomTimerLoop() {
var randomTime = Math.floor(11 * Math.random()) + 20; var randomTime = Math.floor(11 * Math.random()) + 20;
setTimeout(() => { setTimeout(() => {
alert("Provided by pirateIB\nhttps://pirateib.xyz"); alert("Provided by pirateIB\nhttps://pirateib.xyz");
@ -67,7 +82,7 @@ window.onload = function() {
setTimeout(() => { setTimeout(() => {
startRandomTimerLoop(); startRandomTimerLoop();
}, 300000); }, 300000);
}; };*/
/*function generatePDF() { /*function generatePDF() {
const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || []; const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || [];
@ -94,7 +109,6 @@ function generatePDF() {
const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || []; const selectedQuestionIds = JSON.parse(sessionStorage.getItem("selectedQuestionIds")) || [];
if (0 === selectedQuestionIds.length) return alert("Select some questions first!"); if (0 === selectedQuestionIds.length) return alert("Select some questions first!");
// Prompt the user for including markschemes
let includeMarkschemes = null; let includeMarkschemes = null;
while (includeMarkschemes !== "yes" && includeMarkschemes !== "no") { while (includeMarkschemes !== "yes" && includeMarkschemes !== "no") {
includeMarkschemes = prompt("Do you want to include markschemes? (yes/no)").toLowerCase(); includeMarkschemes = prompt("Do you want to include markschemes? (yes/no)").toLowerCase();
@ -177,10 +191,12 @@ if (event.key === 'Escape') {
const fileNameMap = { const fileNameMap = {
'bioqb': 'Biology QB.json', 'bioqb': 'Biology QB.json',
'bioqb25-split': 'Biology 2025 QB split.json', 'bioqb25-split': 'Biology 2025 QB split.json',
'bioqb25-merged': 'Biology 2025 QB merged.json',
'bioqb25-full': 'Biology 2025 QB full.json', 'bioqb25-full': 'Biology 2025 QB full.json',
'bmqb': 'Business Management QB.json', 'bmqb': 'Business Management QB.json',
'chemqb': 'Chemistry QB.json', 'chemqb': 'Chemistry QB.json',
'chemqb25-split': 'Chemistry 2025 QB split.json', 'chemqb25-split': 'Chemistry 2025 QB split.json',
'chemqb25-merged': 'Chemistry 2025 QB merged.json',
'chemqb25-full': 'Chemistry 2025 QB full.json', 'chemqb25-full': 'Chemistry 2025 QB full.json',
'compsciqb': 'Computer Science QB.json', 'compsciqb': 'Computer Science QB.json',
'destechqb': 'Design Technology QB.json', 'destechqb': 'Design Technology QB.json',
@ -193,6 +209,7 @@ const fileNameMap = {
'mathaiqb': 'Math AI QB.json', 'mathaiqb': 'Math AI QB.json',
'phyqb': 'Physics QB.json', 'phyqb': 'Physics QB.json',
'phyqb25-split': 'Physics 2025 QB split.json', 'phyqb25-split': 'Physics 2025 QB split.json',
'phyqb25-merged': 'Physics 2025 QB merged.json',
'phyqb25-full': 'Physics 2025 QB full.json', 'phyqb25-full': 'Physics 2025 QB full.json',
'psychqb': 'Psychology QB.json', 'psychqb': 'Psychology QB.json',
'sehsqb': 'SEHS QB.json' 'sehsqb': 'SEHS QB.json'
@ -278,7 +295,8 @@ async function loadJSON(filename) {
} }
try { try {
const response = await fetch(`../assets/jsonqb/${filename}`); const response = await fetch(`https://pub-59370068cd854c158959e7ca4578e5bd.r2.dev/${filename}`); // ../assets/jsonqb/
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok'); throw new Error('Network response was not ok');
} }
@ -298,17 +316,20 @@ async function loadJSON(filename) {
}; };
}); });
sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('selectedQuestionIds', '[]');
sessionStorage.setItem('visibleIDs', '[]');
setTimeout(() => { setTimeout(() => {
processData(data, filename); processData(data, filename);
hideLoading(); hideLoading();
sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('selectedQuestionIds', '[]');
sessionStorage.setItem('visibleIDs', '[]');
}, 0); }, 0);
} catch (error) { } catch (error) {
console.error('Error fetching JSON:', error); console.error('Error fetching JSON:', error);
hideLoading(); hideLoading();
sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('selectedQuestionIds', '[]');
sessionStorage.setItem('visibleIDs', '[]');
} }
} }
@ -318,9 +339,12 @@ function processData(data, filename) {
jsonDataFetched = true; jsonDataFetched = true;
currentFileName = filename; currentFileName = filename;
jsonData = data;
topics = [...new Set(data.flatMap(item => item.topics))].sort(); topics = [...new Set(data.flatMap(item => item.topics))].sort();
subtopics = [...new Set(data.flatMap(item => item.subtopics))].sort(); subtopics = [...new Set(data.flatMap(item => item.subtopics))].sort();
renderTopics(); renderTopics();
renderSubtopics();
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
@ -379,7 +403,7 @@ function processData(data, filename) {
const content = ` const content = `
<h3>${questionid}</h3> <h3>${questionid}</h3>
<h4><b>Topics:</b> ${topics.join(', ')}</h4> <h4><b>Topics:</b> ${topics.join(', ')}</h4>
<h4><b>Subtopics:</b> ${subtopics.join(', ')}</h4> <h4><details><summary><b>Subtopics</b> </summary>${subtopics.join(', ')}</details></h4>
<div class="square-container">${question}</div> <div class="square-container">${question}</div>
`; `;
@ -453,6 +477,7 @@ function processData(data, filename) {
domCache.rightCol.appendChild(fragment); domCache.rightCol.appendChild(fragment);
updateSquareContainers(); updateSquareContainers();
toggleDownAllQs(); toggleDownAllQs();
toggleFilters();
} }
@ -511,7 +536,29 @@ function createContainer(type, questionid, filename, content, parent) {
parent.appendChild(container); parent.appendChild(container);
} }
function renderSubtopics() {
const subtopicListContainer = document.getElementById('subtopic-select');
subtopicListContainer.innerHTML = '<option value="">All</option>';
const fragment = document.createDocumentFragment();
subtopics.forEach(subtopic => {
const option = document.createElement('option');
option.innerText = subtopic;
option.value = subtopic;
fragment.appendChild(option);
});
subtopicListContainer.appendChild(fragment);
}
function renderTopics() { function renderTopics() {
currentFilters.level = currentFilters.paper = currentFilters.subtopic = null;
document.getElementById('level-select').value = '';
document.getElementById('paper-select').value = '';
document.getElementById('subtopic-select').value = '';
const topicListContainer = document.getElementById('topic-list');
topicListContainer.innerHTML = '';
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
topics.forEach(topic => { topics.forEach(topic => {
@ -519,22 +566,127 @@ function renderTopics() {
label.classList.add('topic-label'); label.classList.add('topic-label');
const checkbox = document.createElement('input'); const checkbox = document.createElement('input');
checkbox.type = 'checkbox'; checkbox.type = 'checkbox';
checkbox.name = 'topic'; checkbox.name = 'topic';
checkbox.value = topic; checkbox.value = topic;
checkbox.addEventListener('change', () => { checkbox.addEventListener('change', () => {
document.querySelectorAll(`div[class*="${topic}"]`) document.getElementById('level-select').value = '';
.forEach(div => div.classList.toggle('hidden')); document.getElementById('paper-select').value = '';
}); document.getElementById('subtopic-select').value = '';
label.append(checkbox, topic); currentFilters.level = null;
fragment.appendChild(label); currentFilters.paper = null;
currentFilters.subtopic = null;
const checkedTopics = Array.from(
document.querySelectorAll('input[name="topic"]:checked')
).map(cb => cb.value);
applyAllFilters('topic', checkedTopics);
applyAllFilters('level', null);
applyAllFilters('paper', null);
applyAllFilters('subtopic', null);
const visibleIds = Array.from(
document.querySelectorAll('#right-col > div:not(.hidden)')
).map(div => div.id);
sessionStorage.setItem('visibleIDs', JSON.stringify(visibleIds));
});
label.append(checkbox, document.createTextNode(topic));
fragment.append(label);
}); });
domCache.leftCol.appendChild(fragment); topicListContainer.appendChild(fragment);
} }
function findRecordById(qid) {
return jsonData.find(item => item.question_id === qid) || {};
}
const currentFilters = {
level: null,
paper: null,
topic: null,
subtopic: null
};
function applyAllFilters(type, value) {
if (type === 'topic' || type === 'subtopic') {
const arr = Array.isArray(value) ? value.filter(Boolean) : [];
currentFilters[type] = arr.length > 0 ? arr : null;
}
else {
currentFilters[type] = value || null;
}
document.querySelectorAll('#right-col > div').forEach(div => {
const qid = div.id;
const record = findRecordById(qid);
let hide = false;
// 1) Level
if (currentFilters.level) {
const ok = currentFilters.level === 'standard'
? qid.includes('.SL.')
: (qid.includes('.HL.') || qid.includes('.AHL.'));
hide ||= !ok;
}
if (currentFilters.paper) {
const pap = currentFilters.paper;
const regex = pap === '1'
? /\.1(?:[ABC])?\./
: new RegExp(`\\.${pap}\\.`);
hide ||= !regex.test(qid);
}
if (!currentFilters.topic) {
hide ||= true;
} else {
const ok = (record.topics || []).some(t =>
currentFilters.topic.includes(t)
);
hide ||= !ok;
}
if (currentFilters.subtopic) {
const ok = (record.subtopics||[])
.some(st => currentFilters.subtopic.includes(st));
hide ||= !ok;
}
div.classList.toggle('hidden', hide);
});
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('paper-select')
.addEventListener('change', e => {
applyAllFilters('paper', e.target.value);
});
document.getElementById('level-select')
.addEventListener('change', e => {
applyAllFilters('level', e.target.value);
});
document
.getElementById('subtopic-select')
.addEventListener('change', e => {
const vals = Array.from(e.target.selectedOptions)
.map(opt => opt.value)
.filter(v => v !== ''); // ← remove the "" entry
applyAllFilters('subtopic', vals);
});
});
function updateSquareContainers() { function updateSquareContainers() {
document.querySelectorAll('.square-container').forEach(container => { document.querySelectorAll('.square-container').forEach(container => {
const firstChild = container.children[0]; const firstChild = container.children[0];
@ -565,4 +717,6 @@ function resetState() {
domCache.rightCol.innerHTML = ''; domCache.rightCol.innerHTML = '';
document.querySelectorAll('.topic-label').forEach(label => label.remove()); document.querySelectorAll('.topic-label').forEach(label => label.remove());
sessionStorage.setItem('selectedQuestionIds', '[]'); sessionStorage.setItem('selectedQuestionIds', '[]');
} sessionStorage.setItem('visibleIDs', '[]');
checkWidth()
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -46,9 +46,8 @@ body {
} }
hr { hr {
height: 0; height: 2px;
color: #000; background-color: #000;
border-top-width: 1px
} }
abbr:where([title]) { abbr:where([title]) {
@ -334,13 +333,66 @@ h4 {
word-break: break-word; word-break: break-word;
} }
#right-col { #right-wrapper {
flex: 4; flex: 4;
display: flex;
flex-direction: column;
width: 80%; width: 80%;
height: 100%;
}
#upper-right-col {
flex: 1;
/*overflow: hidden;*/
word-break: break-word;
}
#right-col {
flex: 9;
/*height: 90%;
width: 80%;*/
overflow-y: auto; overflow-y: auto;
word-break: break-word; word-break: break-word;
} }
.toolbar {
display: flex;
justify-content: flex-end;
flex-wrap: wrap;
gap: 0.5rem;
}
.selectables {
display: flex;
justify-content: left;
flex-wrap: wrap;
gap: 2rem;
}
@media (max-width: 480px) {
.selectables {
flex-direction: column;
align-items: stretch;
gap: 0;
}
}
select {
min-width: 60px;
}
#subtopic-select, #topic-select {
max-width: 100px;
}
#topic-list, .topic-label {
display: block;
}
#topic-list label {
padding-bottom: 0.5rem;
}
#appContainer{ #appContainer{
display: flex; display: flex;
height: 100%; height: 100%;