Spaces:
Running
Running
Upload 10 files
Browse files- src/views/InstructorView.js +1 -1
- src/views/StudentView.js +29 -27
src/views/InstructorView.js
CHANGED
|
@@ -612,7 +612,7 @@ export function setupInstructorEvents() {
|
|
| 612 |
if (confirm('確定要退回此學員的進度嗎?學員將需要重新作答。')) {
|
| 613 |
try {
|
| 614 |
const { resetProgress } = await import("../services/classroom.js");
|
| 615 |
-
await resetProgress(
|
| 616 |
alert('已成功退回,學員將需要重新作答。');
|
| 617 |
window.closeBroadcastModal();
|
| 618 |
} catch (e) {
|
|
|
|
| 612 |
if (confirm('確定要退回此學員的進度嗎?學員將需要重新作答。')) {
|
| 613 |
try {
|
| 614 |
const { resetProgress } = await import("../services/classroom.js");
|
| 615 |
+
await resetProgress(userId, roomCode, challengeId);
|
| 616 |
alert('已成功退回,學員將需要重新作答。');
|
| 617 |
window.closeBroadcastModal();
|
| 618 |
} catch (e) {
|
src/views/StudentView.js
CHANGED
|
@@ -326,19 +326,14 @@ export async function renderStudentView() {
|
|
| 326 |
// Setup Real-time Subscription
|
| 327 |
if (window.currentProgressUnsub) window.currentProgressUnsub();
|
| 328 |
window.currentProgressUnsub = subscribeToUserProgress(userId, (newProgressMap) => {
|
| 329 |
-
// Merge updates
|
| 330 |
-
const updatedProgress =
|
| 331 |
|
| 332 |
-
//
|
| 333 |
const newState = calculateMonsterState(updatedProgress, classSize, userProfile);
|
| 334 |
const fixedContainer = document.getElementById('monster-container-fixed');
|
| 335 |
const currentMonsterId = fixedContainer?.getAttribute('data-monster-id');
|
| 336 |
|
| 337 |
-
// Tolerance for scale check usually not needed if we want scale to update visuals immediately?
|
| 338 |
-
// Actually, scale change usually means totalCompleted changed.
|
| 339 |
-
// If we want smooth growth, replacing DOM resets animation which looks slight jumpy but acceptable.
|
| 340 |
-
// But the user complained about "position reset" (walk cycle reset).
|
| 341 |
-
|
| 342 |
if (fixedContainer && String(currentMonsterId) === String(newState.monster.id)) {
|
| 343 |
// Monster ID is same (no evolution/devolution).
|
| 344 |
// Just update stats tooltip
|
|
@@ -347,29 +342,36 @@ export async function renderStudentView() {
|
|
| 347 |
statsContainer.innerHTML = renderMonsterStats(newState);
|
| 348 |
}
|
| 349 |
|
| 350 |
-
//
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
if (artContainer && newState.currentScale) {
|
| 355 |
-
// Update animation with new scale
|
| 356 |
-
// Note: Modifying 'transform' directly might conflict with keyframes unless keyframes use relative or we update style variable.
|
| 357 |
-
// Keyframes use: scale(${currentScale}). This is hardcoded in specific keyframes string in <style>.
|
| 358 |
-
// We can't easily update keyframes dynamic values without replacing style block.
|
| 359 |
-
|
| 360 |
-
// If totalCompleted changed (level up), user *might* accept a reset because they levelled up.
|
| 361 |
-
// But simply giving a heart shouldn't reset.
|
| 362 |
-
|
| 363 |
-
const oldTotal = parseInt(fixedContainer.querySelector('.bg-gray-900\\/90')?.textContent?.replace('Lv.', '') || '1') - 1;
|
| 364 |
-
if (oldTotal !== newState.totalCompleted) {
|
| 365 |
-
// Level changed -> Scale changed -> Re-render full (reset animation is fine for Level Up)
|
| 366 |
-
monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
|
| 367 |
-
}
|
| 368 |
}
|
| 369 |
} else {
|
| 370 |
-
//
|
| 371 |
monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
|
| 372 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
});
|
| 374 |
|
| 375 |
// Accordion Layout
|
|
|
|
| 326 |
// Setup Real-time Subscription
|
| 327 |
if (window.currentProgressUnsub) window.currentProgressUnsub();
|
| 328 |
window.currentProgressUnsub = subscribeToUserProgress(userId, (newProgressMap) => {
|
| 329 |
+
// Merge updates (Actually newProgressMap is complete source of truth from firestore listener)
|
| 330 |
+
const updatedProgress = newProgressMap;
|
| 331 |
|
| 332 |
+
// 1. Update Monster (Visuals)
|
| 333 |
const newState = calculateMonsterState(updatedProgress, classSize, userProfile);
|
| 334 |
const fixedContainer = document.getElementById('monster-container-fixed');
|
| 335 |
const currentMonsterId = fixedContainer?.getAttribute('data-monster-id');
|
| 336 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
if (fixedContainer && String(currentMonsterId) === String(newState.monster.id)) {
|
| 338 |
// Monster ID is same (no evolution/devolution).
|
| 339 |
// Just update stats tooltip
|
|
|
|
| 342 |
statsContainer.innerHTML = renderMonsterStats(newState);
|
| 343 |
}
|
| 344 |
|
| 345 |
+
// Check level up (totalCompleted) to update scale/animation if needed
|
| 346 |
+
const oldTotal = parseInt(fixedContainer.querySelector('.bg-gray-900\\/90')?.textContent?.replace('Lv.', '') || '1') - 1;
|
| 347 |
+
if (oldTotal !== newState.totalCompleted) {
|
| 348 |
+
monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
}
|
| 350 |
} else {
|
| 351 |
+
// Full Monster Render
|
| 352 |
monsterContainer.innerHTML = renderMonsterSection(updatedProgress, classSize, userProfile);
|
| 353 |
}
|
| 354 |
+
|
| 355 |
+
// 2. Update Task List (Accordions) - Realtime "Reject" support
|
| 356 |
+
const levelGroups = {
|
| 357 |
+
beginner: cachedChallenges.filter(c => c.level === 'beginner'),
|
| 358 |
+
intermediate: cachedChallenges.filter(c => c.level === 'intermediate'),
|
| 359 |
+
advanced: cachedChallenges.filter(c => c.level === 'advanced')
|
| 360 |
+
};
|
| 361 |
+
const levelNames = {
|
| 362 |
+
beginner: "初級 (Beginner)",
|
| 363 |
+
intermediate: "中級 (Intermediate)",
|
| 364 |
+
advanced: "高級 (Advanced)"
|
| 365 |
+
};
|
| 366 |
+
|
| 367 |
+
['beginner', 'intermediate', 'advanced'].forEach(level => {
|
| 368 |
+
const detailEl = document.getElementById(`details-group-${level}`);
|
| 369 |
+
if (detailEl) {
|
| 370 |
+
// renderLevelGroup checks existing DOM for 'open' state, so it persists
|
| 371 |
+
const newHTML = renderLevelGroup(level, levelGroups[level], updatedProgress, levelNames);
|
| 372 |
+
detailEl.outerHTML = newHTML;
|
| 373 |
+
}
|
| 374 |
+
});
|
| 375 |
});
|
| 376 |
|
| 377 |
// Accordion Layout
|