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.
Files changed (2) hide show
  1. package/index.html +44 -194
  2. 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')">&larr; 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 &bull; Student Resource Portal &bull; 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: 'https://cdn.jsdelivr.net/gh/RuneScape/sandboxels@master/index.html', color: '#00897b', icon: 'science' },
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: 'https://hexgl.bkcore.com/play/', color: '#e53935', icon: 'speed' },
524
- { id: 'ClassiCube', name: 'ClassiCube', desc: 'A lightweight multiplayer-compatible classic block building sandbox game for spatial visualization.', url: 'https://www.classicube.net/play/', color: '#00acc1', icon: 'terrain' },
525
- { id: '2048', name: '2048', desc: 'A classic sliding tile puzzle game where you combine matching numbers to reach the 2048 tile.', url: 'https://play2048.co/', color: '#ffb300', icon: 'grid_on' }
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
- const STORAGE_KEY = 'studyup_portal_config_v2';
542
- const BROADCAST_CHANNEL_NAME = 'studyup_sync_channel';
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 stored = localStorage.getItem(STORAGE_KEY);
589
- if (stored) {
590
- return JSON.parse(stored);
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 read from localStorage:", err);
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
- function saveConfig(configObj, broadcast = true) {
516
+ // Save config changes to Express server backend (/api/config)
517
+ async function saveConfig(configObj) {
608
518
  try {
609
- configObj.timestamp = Date.now();
610
- localStorage.setItem(STORAGE_KEY, JSON.stringify(configObj));
611
- localStorage.setItem(STORAGE_KEY + '_timestamp', configObj.timestamp.toString());
612
-
613
- if (broadcast && broadcastChannel) {
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 localStorage:", err);
618
- showNotification("Error saving update to local storage.");
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
- showNotification(`Error attempting to enable fullscreen: ${err.message}`);
779
+ alert(`Error attempting to enable fullscreen: ${err.message}`);
930
780
  });
931
781
  icon.innerText = 'fullscreen_exit';
932
782
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studyup",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "StudyUp Game Portal",
5
5
  "main": "index.html",
6
6
  "scripts": {