studyup 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.html +44 -194
- package/package.json +1 -1
package/index.html
CHANGED
|
@@ -378,34 +378,6 @@
|
|
|
378
378
|
color: var(--text-secondary);
|
|
379
379
|
font-size: 12px;
|
|
380
380
|
}
|
|
381
|
-
|
|
382
|
-
/* Custom modal notification box */
|
|
383
|
-
#notification-modal {
|
|
384
|
-
display: none;
|
|
385
|
-
position: fixed;
|
|
386
|
-
top: 0;
|
|
387
|
-
left: 0;
|
|
388
|
-
width: 100vw;
|
|
389
|
-
height: 100vh;
|
|
390
|
-
background-color: rgba(0, 0, 0, 0.5);
|
|
391
|
-
z-index: 2000;
|
|
392
|
-
align-items: center;
|
|
393
|
-
justify-content: center;
|
|
394
|
-
}
|
|
395
|
-
.notification-box {
|
|
396
|
-
background: white;
|
|
397
|
-
padding: 24px;
|
|
398
|
-
border-radius: 4px;
|
|
399
|
-
box-shadow: var(--shadow-elevation-4);
|
|
400
|
-
max-width: 350px;
|
|
401
|
-
width: 90%;
|
|
402
|
-
text-align: center;
|
|
403
|
-
}
|
|
404
|
-
.notification-message {
|
|
405
|
-
margin-bottom: 20px;
|
|
406
|
-
font-size: 15px;
|
|
407
|
-
color: var(--text-primary);
|
|
408
|
-
}
|
|
409
381
|
</style>
|
|
410
382
|
</head>
|
|
411
383
|
<body>
|
|
@@ -431,7 +403,6 @@
|
|
|
431
403
|
<input type="text" id="class-code-input" placeholder="e.g. 123456" maxlength="6" autofocus>
|
|
432
404
|
<label for="class-code-input">Class Code</label>
|
|
433
405
|
</div>
|
|
434
|
-
|
|
435
406
|
<div id="error-msg">Invalid class code or system currently locked.</div>
|
|
436
407
|
<button class="btn-raised" onclick="verifyCode()">Enter Code</button>
|
|
437
408
|
</div>
|
|
@@ -450,7 +421,6 @@
|
|
|
450
421
|
<h2 style="margin: 0; color: var(--primary-color);">Admin Dashboard</h2>
|
|
451
422
|
<button class="btn-raised" style="width: auto; padding: 8px 16px;" onclick="switchView('portal-view')">← Back to Portal</button>
|
|
452
423
|
</div>
|
|
453
|
-
|
|
454
424
|
<!-- Global Control Section -->
|
|
455
425
|
<div class="admin-section">
|
|
456
426
|
<h2>Global System Controls</h2>
|
|
@@ -463,7 +433,6 @@
|
|
|
463
433
|
<button id="global-toggle-btn" class="btn-raised btn-warning" onclick="toggleGlobalBlock()">Temporarily Block All Codes</button>
|
|
464
434
|
</div>
|
|
465
435
|
</div>
|
|
466
|
-
|
|
467
436
|
<!-- Create Code Section -->
|
|
468
437
|
<div class="admin-section">
|
|
469
438
|
<h2>Create New Class Code</h2>
|
|
@@ -476,7 +445,6 @@
|
|
|
476
445
|
</div>
|
|
477
446
|
<div id="admin-error-msg" style="color: var(--error-color); font-size: 13px; margin-top: 8px; display: none;"></div>
|
|
478
447
|
</div>
|
|
479
|
-
|
|
480
448
|
<!-- Manage Codes Section -->
|
|
481
449
|
<div class="admin-section">
|
|
482
450
|
<h2>Manage Existing Codes & Game Permissions</h2>
|
|
@@ -500,29 +468,21 @@
|
|
|
500
468
|
</div>
|
|
501
469
|
</div>
|
|
502
470
|
<div class="modal-body">
|
|
503
|
-
<iframe id="game-iframe" src="" title="Game View" sandbox="allow-scripts allow-same-origin allow-modals allow-pointer-lock allow-downloads"></iframe>
|
|
471
|
+
<iframe id="game-iframe" src="" title="Game View" sandbox="allow-scripts allow-same-origin allow-modals allow-pointer-lock allow-downloads allow-forms allow-popups"></iframe>
|
|
504
472
|
</div>
|
|
505
473
|
</div>
|
|
506
474
|
</div>
|
|
507
475
|
|
|
508
|
-
<!-- Custom Notification Modal -->
|
|
509
|
-
<div id="notification-modal">
|
|
510
|
-
<div class="notification-box">
|
|
511
|
-
<div id="notification-text" class="notification-message">Message here</div>
|
|
512
|
-
<button class="btn-raised" onclick="closeNotification()">OK</button>
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
515
|
-
|
|
516
476
|
<footer>
|
|
517
477
|
StudyUp • Student Resource Portal • 2026
|
|
518
478
|
</footer>
|
|
519
479
|
|
|
520
480
|
<script>
|
|
521
481
|
const AVAILABLE_GAMES = [
|
|
522
|
-
{ id: 'Sandboxels', name: 'Sandboxels', desc: 'A falling sand and physics sandbox game where you can mix elements and trigger chemical reactions.', url: '
|
|
523
|
-
{ id: 'HexGL', name: 'HexGL', desc: 'A futuristic 3D WebGL racing game built with HTML5 where you pilot a high-speed craft through complex tracks.', url: '
|
|
524
|
-
{ id: 'ClassiCube', name: 'ClassiCube', desc: 'A lightweight multiplayer-compatible classic block building sandbox game for spatial visualization.', url: '
|
|
525
|
-
{ id: '2048', name: '2048', desc: 'A classic sliding tile puzzle game where you combine matching numbers to reach the 2048 tile.', url: '
|
|
482
|
+
{ id: 'Sandboxels', name: 'Sandboxels', desc: 'A falling sand and physics sandbox game where you can mix elements and trigger chemical reactions.', url: 'sandboxels-package/sandboxels-1.13.2/index.html', color: '#00897b', icon: 'science' },
|
|
483
|
+
{ id: 'HexGL', name: 'HexGL', desc: 'A futuristic 3D WebGL racing game built with HTML5 where you pilot a high-speed craft through complex tracks.', url: 'hexgl-package/HexGL-master/index.html', color: '#e53935', icon: 'speed' },
|
|
484
|
+
{ id: 'ClassiCube', name: 'ClassiCube', desc: 'A lightweight multiplayer-compatible classic block building sandbox game for spatial visualization.', url: 'classicube-package/index.html', color: '#00acc1', icon: 'terrain' },
|
|
485
|
+
{ id: '2048', name: '2048', desc: 'A classic sliding tile puzzle game where you combine matching numbers to reach the 2048 tile.', url: '2048-package/index.html', color: '#ffb300', icon: 'grid_on' }
|
|
526
486
|
];
|
|
527
487
|
|
|
528
488
|
const ADMIN_APP = {
|
|
@@ -538,151 +498,45 @@
|
|
|
538
498
|
}
|
|
539
499
|
};
|
|
540
500
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
// Session state tracking for current browser tab/window
|
|
545
|
-
let currentSession = {
|
|
546
|
-
isLoggedIn: false,
|
|
547
|
-
code: null, // null, '814212', or student class code
|
|
548
|
-
isAdmin: false
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
let broadcastChannel = null;
|
|
552
|
-
try {
|
|
553
|
-
if (window.BroadcastChannel) {
|
|
554
|
-
broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
|
|
555
|
-
broadcastChannel.onmessage = (event) => {
|
|
556
|
-
handleSyncEvent(event.data);
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
|
-
} catch (e) {
|
|
560
|
-
console.warn("BroadcastChannel not supported in this environment.", e);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Listen to storage events across tabs/devices
|
|
564
|
-
window.addEventListener('storage', (event) => {
|
|
565
|
-
if (event.key === STORAGE_KEY || event.key === STORAGE_KEY + '_timestamp') {
|
|
566
|
-
const config = getConfig();
|
|
567
|
-
evaluateCurrentSession(config);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
// Periodic polling interval to guarantee instant updates across all open tabs/devices
|
|
572
|
-
setInterval(() => {
|
|
573
|
-
const config = getConfig();
|
|
574
|
-
evaluateCurrentSession(config);
|
|
575
|
-
}, 1000);
|
|
576
|
-
|
|
577
|
-
function showNotification(msg) {
|
|
578
|
-
document.getElementById('notification-text').innerText = msg;
|
|
579
|
-
document.getElementById('notification-modal').style.display = 'flex';
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function closeNotification() {
|
|
583
|
-
document.getElementById('notification-modal').style.display = 'none';
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
function getConfig() {
|
|
501
|
+
// Fetch config from Express server backend (/api/config)
|
|
502
|
+
async function getConfig() {
|
|
587
503
|
try {
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
}
|
|
504
|
+
const response = await fetch('/api/config');
|
|
505
|
+
const data = await response.json();
|
|
506
|
+
return data;
|
|
592
507
|
} catch (err) {
|
|
593
|
-
console.error("Failed to
|
|
508
|
+
console.error("Failed to fetch configuration from server:", err);
|
|
509
|
+
return {
|
|
510
|
+
globalBlock: false,
|
|
511
|
+
codes: { "123456": { games: ["Sandboxels", "HexGL", "ClassiCube", "2048"] } }
|
|
512
|
+
};
|
|
594
513
|
}
|
|
595
|
-
|
|
596
|
-
const defaultConfig = {
|
|
597
|
-
globalBlock: false,
|
|
598
|
-
timestamp: Date.now(),
|
|
599
|
-
codes: {
|
|
600
|
-
"123456": { games: ["Sandboxels", "HexGL", "ClassiCube", "2048"] }
|
|
601
|
-
}
|
|
602
|
-
};
|
|
603
|
-
saveConfig(defaultConfig, false);
|
|
604
|
-
return defaultConfig;
|
|
605
514
|
}
|
|
606
515
|
|
|
607
|
-
|
|
516
|
+
// Save config changes to Express server backend (/api/config)
|
|
517
|
+
async function saveConfig(configObj) {
|
|
608
518
|
try {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
broadcastChannel.postMessage({ type: 'CONFIG_UPDATED', config: configObj });
|
|
615
|
-
}
|
|
519
|
+
await fetch('/api/config', {
|
|
520
|
+
method: 'POST',
|
|
521
|
+
headers: { 'Content-Type': 'application/json' },
|
|
522
|
+
body: JSON.stringify(configObj)
|
|
523
|
+
});
|
|
616
524
|
} catch (err) {
|
|
617
|
-
console.error("Failed to save to
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function handleSyncEvent(data) {
|
|
623
|
-
if (data && data.type === 'CONFIG_UPDATED') {
|
|
624
|
-
evaluateCurrentSession(data.config);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Evaluate whether the logged-in user is still valid based on latest config
|
|
629
|
-
function evaluateCurrentSession(config) {
|
|
630
|
-
const loginViewActive = document.getElementById('login-view').classList.contains('active');
|
|
631
|
-
if (!currentSession.isLoggedIn || loginViewActive) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
// Master Admin is never automatically kicked out unless global lock happens and they choose to log out,
|
|
636
|
-
// but let's check if regular student code is valid or globally blocked.
|
|
637
|
-
if (config.globalBlock && !currentSession.isAdmin) {
|
|
638
|
-
forceLogout("System is temporarily locked by administrator.");
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (currentSession.isAdmin) {
|
|
643
|
-
// Admin dashboard might be open, update dashboard if view is active
|
|
644
|
-
if (document.getElementById('admin-view').classList.contains('active')) {
|
|
645
|
-
loadAdminDashboard();
|
|
646
|
-
}
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// Student session validation
|
|
651
|
-
const code = currentSession.code;
|
|
652
|
-
if (!config.codes || !config.codes[code]) {
|
|
653
|
-
forceLogout("Your class code has been modified or revoked.");
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// Update student dashboard games dynamically in real time
|
|
658
|
-
const allowedGames = config.codes[code].games || [];
|
|
659
|
-
if (document.getElementById('portal-view').classList.contains('active')) {
|
|
660
|
-
loadStudentDashboard(allowedGames, false);
|
|
525
|
+
console.error("Failed to save changes to server:", err);
|
|
526
|
+
alert("Error saving update to server.");
|
|
661
527
|
}
|
|
662
528
|
}
|
|
663
529
|
|
|
664
|
-
function forceLogout(reason) {
|
|
665
|
-
currentSession = { isLoggedIn: false, code: null, isAdmin: false };
|
|
666
|
-
closeGame();
|
|
667
|
-
switchView('login-view');
|
|
668
|
-
document.getElementById('current-user-badge').innerText = '';
|
|
669
|
-
const errorMsg = document.getElementById('error-msg');
|
|
670
|
-
errorMsg.innerText = reason;
|
|
671
|
-
errorMsg.style.display = 'block';
|
|
672
|
-
document.getElementById('class-code-input').value = '';
|
|
673
|
-
document.getElementById('class-code-input').focus();
|
|
674
|
-
}
|
|
675
|
-
|
|
676
530
|
document.getElementById('class-code-input').addEventListener('keypress', function (e) {
|
|
677
531
|
if (e.key === 'Enter') {
|
|
678
532
|
verifyCode();
|
|
679
533
|
}
|
|
680
534
|
});
|
|
681
535
|
|
|
682
|
-
function verifyCode() {
|
|
536
|
+
async function verifyCode() {
|
|
683
537
|
const inputVal = document.getElementById('class-code-input').value.trim();
|
|
684
538
|
const errorMsg = document.getElementById('error-msg');
|
|
685
|
-
const config = getConfig();
|
|
539
|
+
const config = await getConfig();
|
|
686
540
|
|
|
687
541
|
if (config.globalBlock && inputVal !== '814212') {
|
|
688
542
|
errorMsg.innerText = "System is temporarily locked by administrator.";
|
|
@@ -694,7 +548,6 @@
|
|
|
694
548
|
if (inputVal === '814212') {
|
|
695
549
|
errorMsg.style.display = 'none';
|
|
696
550
|
document.getElementById('class-code-input').value = '';
|
|
697
|
-
currentSession = { isLoggedIn: true, code: '814212', isAdmin: true };
|
|
698
551
|
switchView('portal-view');
|
|
699
552
|
document.getElementById('current-user-badge').innerText = 'Master Admin Mode';
|
|
700
553
|
|
|
@@ -703,7 +556,6 @@
|
|
|
703
556
|
} else if (config.codes && config.codes[inputVal]) {
|
|
704
557
|
errorMsg.style.display = 'none';
|
|
705
558
|
document.getElementById('class-code-input').value = '';
|
|
706
|
-
currentSession = { isLoggedIn: true, code: inputVal, isAdmin: false };
|
|
707
559
|
switchView('portal-view');
|
|
708
560
|
document.getElementById('current-user-badge').innerText = `Class Code: ${inputVal}`;
|
|
709
561
|
loadStudentDashboard(config.codes[inputVal].games || [], false);
|
|
@@ -720,7 +572,6 @@
|
|
|
720
572
|
}
|
|
721
573
|
|
|
722
574
|
function logout() {
|
|
723
|
-
currentSession = { isLoggedIn: false, code: null, isAdmin: false };
|
|
724
575
|
closeGame();
|
|
725
576
|
switchView('login-view');
|
|
726
577
|
document.getElementById('current-user-badge').innerText = '';
|
|
@@ -781,8 +632,8 @@
|
|
|
781
632
|
});
|
|
782
633
|
}
|
|
783
634
|
|
|
784
|
-
function loadAdminDashboard() {
|
|
785
|
-
const config = getConfig();
|
|
635
|
+
async function loadAdminDashboard() {
|
|
636
|
+
const config = await getConfig();
|
|
786
637
|
const isBlocked = config.globalBlock;
|
|
787
638
|
|
|
788
639
|
const statusText = document.getElementById('global-status-text');
|
|
@@ -803,7 +654,6 @@
|
|
|
803
654
|
listContainer.innerHTML = '';
|
|
804
655
|
|
|
805
656
|
const studentCodes = Object.keys(config.codes || {});
|
|
806
|
-
|
|
807
657
|
if (studentCodes.length === 0) {
|
|
808
658
|
listContainer.innerHTML = `<p style="color: var(--text-secondary); font-style: italic;">No active student codes found. Create one above.</p>`;
|
|
809
659
|
return;
|
|
@@ -837,14 +687,14 @@
|
|
|
837
687
|
});
|
|
838
688
|
}
|
|
839
689
|
|
|
840
|
-
function toggleGlobalBlock() {
|
|
841
|
-
const config = getConfig();
|
|
690
|
+
async function toggleGlobalBlock() {
|
|
691
|
+
const config = await getConfig();
|
|
842
692
|
config.globalBlock = !config.globalBlock;
|
|
843
|
-
saveConfig(config);
|
|
693
|
+
await saveConfig(config);
|
|
844
694
|
loadAdminDashboard();
|
|
845
695
|
}
|
|
846
696
|
|
|
847
|
-
function createNewCode() {
|
|
697
|
+
async function createNewCode() {
|
|
848
698
|
const input = document.getElementById('new-code-input');
|
|
849
699
|
const err = document.getElementById('admin-error-msg');
|
|
850
700
|
const val = input.value.trim();
|
|
@@ -860,7 +710,7 @@
|
|
|
860
710
|
return;
|
|
861
711
|
}
|
|
862
712
|
|
|
863
|
-
const config = getConfig();
|
|
713
|
+
const config = await getConfig();
|
|
864
714
|
if (!config.codes) config.codes = {};
|
|
865
715
|
|
|
866
716
|
if (config.codes[val]) {
|
|
@@ -871,22 +721,22 @@
|
|
|
871
721
|
|
|
872
722
|
err.style.display = 'none';
|
|
873
723
|
config.codes[val] = { games: AVAILABLE_GAMES.map(g => g.id) };
|
|
874
|
-
saveConfig(config);
|
|
724
|
+
await saveConfig(config);
|
|
875
725
|
input.value = '';
|
|
876
726
|
loadAdminDashboard();
|
|
877
727
|
}
|
|
878
728
|
|
|
879
|
-
function deleteCode(code) {
|
|
729
|
+
async function deleteCode(code) {
|
|
880
730
|
if (confirm(`Are you sure you want to delete code ${code}?`)) {
|
|
881
|
-
const config = getConfig();
|
|
731
|
+
const config = await getConfig();
|
|
882
732
|
delete config.codes[code];
|
|
883
|
-
saveConfig(config);
|
|
733
|
+
await saveConfig(config);
|
|
884
734
|
loadAdminDashboard();
|
|
885
735
|
}
|
|
886
736
|
}
|
|
887
737
|
|
|
888
|
-
function updateCodePermission(code, gameId, isChecked) {
|
|
889
|
-
const config = getConfig();
|
|
738
|
+
async function updateCodePermission(code, gameId, isChecked) {
|
|
739
|
+
const config = await getConfig();
|
|
890
740
|
if (!config.codes[code].games) config.codes[code].games = [];
|
|
891
741
|
|
|
892
742
|
if (isChecked) {
|
|
@@ -896,13 +746,13 @@
|
|
|
896
746
|
} else {
|
|
897
747
|
config.codes[code].games = config.codes[code].games.filter(id => id !== gameId);
|
|
898
748
|
}
|
|
899
|
-
saveConfig(config);
|
|
749
|
+
await saveConfig(config);
|
|
900
750
|
}
|
|
901
751
|
|
|
902
|
-
function allowAllForCode(code) {
|
|
903
|
-
const config = getConfig();
|
|
752
|
+
async function allowAllForCode(code) {
|
|
753
|
+
const config = await getConfig();
|
|
904
754
|
config.codes[code].games = AVAILABLE_GAMES.map(g => g.id);
|
|
905
|
-
saveConfig(config);
|
|
755
|
+
await saveConfig(config);
|
|
906
756
|
loadAdminDashboard();
|
|
907
757
|
}
|
|
908
758
|
|
|
@@ -926,7 +776,7 @@
|
|
|
926
776
|
|
|
927
777
|
if (!document.fullscreenElement) {
|
|
928
778
|
modalBox.requestFullscreen().catch(err => {
|
|
929
|
-
|
|
779
|
+
alert(`Error attempting to enable fullscreen: ${err.message}`);
|
|
930
780
|
});
|
|
931
781
|
icon.innerText = 'fullscreen_exit';
|
|
932
782
|
} else {
|