Spaces:
Running
Running
Upload 10 files
Browse files- src/views/AdminView.js +6 -1
- src/views/StudentView.js +100 -0
src/views/AdminView.js
CHANGED
|
@@ -52,6 +52,10 @@ export function renderAdminView() {
|
|
| 52 |
<label class="block text-gray-400 mb-1">連結 (GeminiCanvas Link/Code)</label>
|
| 53 |
<input type="text" id="edit-link" class="w-full bg-gray-900 border border-gray-600 rounded p-2 text-white">
|
| 54 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
<div>
|
| 56 |
<label class="block text-gray-400 mb-1">排序 (Order)</label>
|
| 57 |
<input type="number" id="edit-order" class="w-full bg-gray-900 border border-gray-600 rounded p-2 text-white" value="1">
|
|
@@ -107,6 +111,7 @@ export function setupAdminEvents() {
|
|
| 107 |
level: document.getElementById('edit-level').value,
|
| 108 |
description: document.getElementById('edit-desc').value,
|
| 109 |
link: document.getElementById('edit-link').value,
|
|
|
|
| 110 |
order: parseInt(document.getElementById('edit-order').value) || 0
|
| 111 |
};
|
| 112 |
|
|
@@ -203,10 +208,10 @@ window.openModal = function (challenge = null, defaultLevel = 'beginner') {
|
|
| 203 |
// Reset or Fill
|
| 204 |
document.getElementById('edit-id').value = challenge ? challenge.id : '';
|
| 205 |
document.getElementById('edit-title').value = challenge ? challenge.title : '';
|
| 206 |
-
// Use challenge level if editing, otherwise use defaultLevel passed from section button
|
| 207 |
document.getElementById('edit-level').value = challenge ? challenge.level : defaultLevel;
|
| 208 |
document.getElementById('edit-desc').value = challenge ? challenge.description : '';
|
| 209 |
document.getElementById('edit-link').value = challenge ? challenge.link : '';
|
|
|
|
| 210 |
document.getElementById('edit-order').value = challenge ? challenge.order : '1';
|
| 211 |
|
| 212 |
title.textContent = challenge ? '編輯題目' : '新增題目';
|
|
|
|
| 52 |
<label class="block text-gray-400 mb-1">連結 (GeminiCanvas Link/Code)</label>
|
| 53 |
<input type="text" id="edit-link" class="w-full bg-gray-900 border border-gray-600 rounded p-2 text-white">
|
| 54 |
</div>
|
| 55 |
+
<div>
|
| 56 |
+
<label class="block text-gray-400 mb-1">參考答案 (Reference Answer)</label>
|
| 57 |
+
<textarea id="edit-reference" rows="4" class="w-full bg-gray-900 border border-gray-600 rounded p-2 text-white placeholder-gray-500" placeholder="提示詞沒有標準答案,只要能有效修正程式,就是好的提示詞,此答案僅供參考..."></textarea>
|
| 58 |
+
</div>
|
| 59 |
<div>
|
| 60 |
<label class="block text-gray-400 mb-1">排序 (Order)</label>
|
| 61 |
<input type="number" id="edit-order" class="w-full bg-gray-900 border border-gray-600 rounded p-2 text-white" value="1">
|
|
|
|
| 111 |
level: document.getElementById('edit-level').value,
|
| 112 |
description: document.getElementById('edit-desc').value,
|
| 113 |
link: document.getElementById('edit-link').value,
|
| 114 |
+
reference: document.getElementById('edit-reference').value,
|
| 115 |
order: parseInt(document.getElementById('edit-order').value) || 0
|
| 116 |
};
|
| 117 |
|
|
|
|
| 208 |
// Reset or Fill
|
| 209 |
document.getElementById('edit-id').value = challenge ? challenge.id : '';
|
| 210 |
document.getElementById('edit-title').value = challenge ? challenge.title : '';
|
|
|
|
| 211 |
document.getElementById('edit-level').value = challenge ? challenge.level : defaultLevel;
|
| 212 |
document.getElementById('edit-desc').value = challenge ? challenge.description : '';
|
| 213 |
document.getElementById('edit-link').value = challenge ? challenge.link : '';
|
| 214 |
+
document.getElementById('edit-reference').value = challenge ? (challenge.reference || '') : '';
|
| 215 |
document.getElementById('edit-order').value = challenge ? challenge.order : '1';
|
| 216 |
|
| 217 |
title.textContent = challenge ? '編輯題目' : '新增題目';
|
src/views/StudentView.js
CHANGED
|
@@ -477,6 +477,17 @@ export function setupStudentEvents() {
|
|
| 477 |
app.innerHTML = await renderStudentView();
|
| 478 |
}
|
| 479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
} catch (error) {
|
| 481 |
console.error(error);
|
| 482 |
btn.textContent = originalText;
|
|
@@ -584,6 +595,95 @@ window.closePeerModal = () => {
|
|
| 584 |
document.getElementById('peer-modal').classList.add('hidden');
|
| 585 |
};
|
| 586 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 587 |
// Helper to render a level group (Accordion)
|
| 588 |
function renderLevelGroup(level, tasks, userProgress, levelNames) {
|
| 589 |
const detailsId = `details-group-${level}`;
|
|
|
|
| 477 |
app.innerHTML = await renderStudentView();
|
| 478 |
}
|
| 479 |
|
| 480 |
+
// 檢查是否已完成該層級所有題目
|
| 481 |
+
const levelTasks = levelGroups[level] || [];
|
| 482 |
+
const completedCount = levelTasks.filter(t => newProgress[t.id]?.status === 'completed').length;
|
| 483 |
+
if (completedCount === levelTasks.length && levelTasks.length > 0) {
|
| 484 |
+
setTimeout(() => {
|
| 485 |
+
if (confirm(`恭喜!您已完成${levelNames[level]}所有關卡!是否要與參考答案進行核對?`)) {
|
| 486 |
+
window.showReferenceComparison(level, levelTasks, newProgress);
|
| 487 |
+
}
|
| 488 |
+
}, 500);
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
} catch (error) {
|
| 492 |
console.error(error);
|
| 493 |
btn.textContent = originalText;
|
|
|
|
| 595 |
document.getElementById('peer-modal').classList.add('hidden');
|
| 596 |
};
|
| 597 |
|
| 598 |
+
function renderReferenceModal(level, tasks, progress) {
|
| 599 |
+
const levelNames = {
|
| 600 |
+
beginner: "初級 (Beginner)",
|
| 601 |
+
intermediate: "中級 (Intermediate)",
|
| 602 |
+
advanced: "高級 (Advanced)"
|
| 603 |
+
};
|
| 604 |
+
|
| 605 |
+
let listHtml = tasks.map(c => {
|
| 606 |
+
const p = progress[c.id] || {};
|
| 607 |
+
const refAnswer = c.reference || '尚無參考答案\n(No reference answer provided)';
|
| 608 |
+
return `
|
| 609 |
+
<div class="bg-gray-800 rounded-xl p-6 border border-gray-600 shadow-md transform transition-all hover:-translate-y-1 hover:shadow-cyan-900/50">
|
| 610 |
+
<h4 class="text-xl text-cyan-400 font-bold mb-4 drop-shadow-md pb-2 border-b border-gray-700">${c.title}</h4>
|
| 611 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 612 |
+
<div class="flex flex-col">
|
| 613 |
+
<div class="text-sm font-bold text-gray-400 mb-2 uppercase tracking-wide flex items-center">
|
| 614 |
+
<svg class="w-4 h-4 mr-1 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
|
| 615 |
+
您的提示詞 (Your Prompt):
|
| 616 |
+
</div>
|
| 617 |
+
<div class="flex-1 bg-black/60 border border-gray-600 p-4 rounded-lg font-mono text-gray-200 text-sm whitespace-pre-wrap leading-relaxed shadow-[inset_0_2px_10px_rgba(0,0,0,0.5)] overflow-x-auto">${p.submission_prompt || '<span class="text-gray-600 italic">未提交 (Not Submitted)</span>'}</div>
|
| 618 |
+
</div>
|
| 619 |
+
<div class="flex flex-col">
|
| 620 |
+
<div class="text-sm font-bold text-green-400 mb-2 uppercase tracking-wide flex items-center">
|
| 621 |
+
<svg class="w-4 h-4 mr-1 text-green-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
| 622 |
+
參考答案 (Reference Answer):
|
| 623 |
+
</div>
|
| 624 |
+
<div class="flex-1 bg-green-900/10 border border-green-700/50 p-4 rounded-lg font-mono text-green-300 text-sm whitespace-pre-wrap leading-relaxed shadow-[inset_0_2px_10px_rgba(34,197,94,0.1)] overflow-x-auto">${refAnswer}</div>
|
| 625 |
+
</div>
|
| 626 |
+
</div>
|
| 627 |
+
</div>
|
| 628 |
+
`;
|
| 629 |
+
}).join('');
|
| 630 |
+
|
| 631 |
+
return `
|
| 632 |
+
<div id="reference-modal" class="fixed inset-0 bg-gray-900/95 backdrop-blur-md flex items-center justify-center z-[100] p-4 transition-all duration-300">
|
| 633 |
+
<div class="bg-gray-800 rounded-2xl w-full max-w-5xl max-h-[90vh] flex flex-col border border-gray-700 shadow-[0_0_50px_rgba(6,182,212,0.15)] overflow-hidden scale-100 origin-center animate-fade-in-up">
|
| 634 |
+
<div class="bg-gradient-to-r from-blue-900 to-cyan-900 p-6 flex justify-between items-center border-b border-cyan-800">
|
| 635 |
+
<div>
|
| 636 |
+
<h3 class="text-2xl font-black text-white flex items-center space-x-3 drop-shadow-md">
|
| 637 |
+
<span class="text-3xl filter drop-shadow-[0_0_5px_rgba(34,211,238,0.8)]">✨</span>
|
| 638 |
+
<span>${levelNames[level]} - 參考答案核對</span>
|
| 639 |
+
</h3>
|
| 640 |
+
</div>
|
| 641 |
+
<button onclick="window.closeReferenceModal()" class="text-cyan-200 hover:text-white bg-cyan-900/50 hover:bg-cyan-800 p-2 rounded-full transition-all">
|
| 642 |
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
|
| 643 |
+
</button>
|
| 644 |
+
</div>
|
| 645 |
+
|
| 646 |
+
<div class="p-6 flex-1 overflow-y-auto space-y-6 bg-gray-900 custom-scrollbar shadow-inner">
|
| 647 |
+
${listHtml}
|
| 648 |
+
</div>
|
| 649 |
+
|
| 650 |
+
<div class="bg-gray-800 p-5 border-t border-gray-700 text-center">
|
| 651 |
+
<p class="text-yellow-400 text-sm md:text-base font-bold flex flex-col md:flex-row items-center justify-center md:space-x-2">
|
| 652 |
+
<span class="text-xl mb-1 md:mb-0">💡</span>
|
| 653 |
+
<span>提示詞沒有標準答案,只要能有效修正程式,就是好的提示詞,此答案僅供參考。</span>
|
| 654 |
+
</p>
|
| 655 |
+
</div>
|
| 656 |
+
</div>
|
| 657 |
+
</div>
|
| 658 |
+
<style>
|
| 659 |
+
@keyframes fade-in-up {
|
| 660 |
+
0% { opacity: 0; transform: translateY(20px) scale(0.95); }
|
| 661 |
+
100% { opacity: 1; transform: translateY(0) scale(1); }
|
| 662 |
+
}
|
| 663 |
+
.animate-fade-in-up {
|
| 664 |
+
animation: fade-in-up 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
| 665 |
+
}
|
| 666 |
+
</style>
|
| 667 |
+
`;
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
window.showReferenceComparison = (level, tasks, progress) => {
|
| 671 |
+
const existing = document.getElementById('reference-modal');
|
| 672 |
+
if (existing) existing.remove();
|
| 673 |
+
|
| 674 |
+
const div = document.createElement('div');
|
| 675 |
+
div.innerHTML = renderReferenceModal(level, tasks, progress);
|
| 676 |
+
|
| 677 |
+
// Cleanup any existing event listeners before adding new modal to prevent issues
|
| 678 |
+
document.body.appendChild(div);
|
| 679 |
+
};
|
| 680 |
+
|
| 681 |
+
window.closeReferenceModal = () => {
|
| 682 |
+
const el = document.getElementById('reference-modal');
|
| 683 |
+
// Wrap removal in transition logic if needed, simple remove for now
|
| 684 |
+
if (el) el.parentElement.remove();
|
| 685 |
+
};
|
| 686 |
+
|
| 687 |
// Helper to render a level group (Accordion)
|
| 688 |
function renderLevelGroup(level, tasks, userProgress, levelNames) {
|
| 689 |
const detailsId = `details-group-${level}`;
|