1/* ================================================================ 2 ΡÎÎÎÎ ÎÎÎΤÎΣÎΩΠâ menu.js 3 Drawer ÏÏ Î¸Î¼Î¯ÏεÏν: ÏαÏάÏαÏη, ειδοÏοιήÏειÏ, κλίμακα ÏολογιÏν/KPI, 4 ÏαÏÏη, οθÏνη ÎÎÎÎ, ÏÏοεÏιλογÎÏ, βοήθεια. 5 ÎÏικοινÏνεί με script.js αÏοκλειÏÏικά μÎÏÏ window.ExamTimer. 6================================================================ */ 7 8(function () { 9 'use strict'; 10 11 function init() { 12 const ET = window.ExamTimer; 13 if (!ET) { 14 console.error('menu.js: window.ExamTimer δεν βÏÎθηκε'); 15 return; 16 } 17 18 const $ = id => document.getElementById(id); 19 20 /* ---- DOM refs ---- */ 21 const menuBtn = $('menuBtn'); 22 const menuDrawer = $('menuDrawer'); 23 const menuBackdrop = $('menuBackdrop'); 24 const menuCloseBtn = $('menuCloseBtn'); 25 const pauseBanner = $('pauseBanner'); 26 const pauseToggleBtn = $('pauseToggleBtn'); 27 const endScreenOverlay = $('endScreenOverlay'); 28 const kpiScaleSlider = $('kpiScaleSlider'); 29 const kpiScaleReadout = $('kpiScaleReadout'); 30 const sidePanelFontSlider = $('sidePanelFontSlider'); 31 const sidePanelFontReadout = $('sidePanelFontReadout'); 32 const helpBtn = $('helpBtn'); 33 const helpBackdrop = $('helpBackdrop'); 34 const helpModalClose = $('helpModalClose'); 35 const zoomOutBtn = $('zoomOutBtn'); 36 const zoomInBtn = $('zoomInBtn'); 37 const zoomPctLabel = $('zoomPctLabel'); 38 39 /* ================================================================ 40 DRAWER OPEN / CLOSE 41 ================================================================ */ 42 function openMenu() { 43 menuDrawer.classList.add('open'); 44 menuBackdrop.classList.add('open'); 45 menuCloseBtn.focus(); 46 } 47 48 function closeMenu() { 49 menuDrawer.classList.remove('open'); 50 menuBackdrop.classList.remove('open'); 51 menuBtn.focus(); 52 } 53 54 menuBtn.addEventListener('click', openMenu); 55 menuCloseBtn.addEventListener('click', closeMenu); 56 menuBackdrop.addEventListener('click', closeMenu); 57 document.addEventListener('keydown', function (e) { 58 if (e.key === 'Escape' && menuDrawer.classList.contains('open')) closeMenu(); 59 }); 60 61 /* ================================================================ 62 1. Î ÎΡÎΤÎΣΠΧΡÎÎÎÎ¥ 63 ================================================================ */ 64 document.querySelectorAll('.ext-btn').forEach(function (btn) { 65 btn.addEventListener('click', function () { 66 ET.addTime(parseInt(this.dataset.mins, 10)); 67 }); 68 }); 69 70 /* ================================================================ 71 2. ÎÎÎÎÎ ÎÎÎΣÎÎΣ 72 ================================================================ */ 73 function bindNotifMaster(masterId, subId, enabledKey) { 74 const master = $(masterId); 75 const sub = $(subId); 76 master.addEventListener('change', function () { 77 ET.state[enabledKey] = this.checked; 78 sub.classList.toggle('disabled', !this.checked); 79 }); 80 } 81 82 function bindSubCheck(checkId, stateKey) { 83 $(checkId).addEventListener('change', function () { 84 ET.state[stateKey] = this.checked; 85 }); 86 } 87 88 bindNotifMaster('notif30Master', 'notif30Sub', 'notif30Enabled'); 89 bindSubCheck('notif30Sound', 'notif30Sound'); 90 bindSubCheck('notif30Flash', 'notif30Flash'); 91 bindSubCheck('notif30Color', 'notif30Color'); 92 93 bindNotifMaster('notif15Master', 'notif15Sub', 'notif15Enabled'); 94 bindSubCheck('notif15Sound', 'notif15Sound'); 95 bindSubCheck('notif15Flash', 'notif15Flash'); 96 bindSubCheck('notif15Color', 'notif15Color'); 97 98 /* ================================================================ 99 3. ÎÎÎÎÎÎÎΣ ÎÎÎÎÎÎΥΣ 100 ================================================================ */ 101 function bindScaleSlider(sliderId, readoutId, cssVar) { 102 const slider = $(sliderId); 103 const readout = $(readoutId); 104 slider.addEventListener('input', function () { 105 const v = parseFloat(this.value); 106 document.documentElement.style.setProperty(cssVar, v); 107 readout.textContent = Math.round(v) + '%'; 108 }); 109 } 110 111 bindScaleSlider('analogScaleSlider', 'analogScaleReadout', '--analog-pct'); 112 bindScaleSlider('digitalScaleSlider', 'digitalScaleReadout', '--digital-pct'); 113 bindScaleSlider('countdownScaleSlider', 'countdownScaleReadout', '--countdown-pct'); 114 115 kpiScaleSlider.addEventListener('input', function () { 116 const v = parseFloat(this.value); 117 ET.state.kpiScale = v; 118 document.documentElement.style.setProperty('--kpi-scale', v); 119 kpiScaleReadout.textContent = v.toFixed(1) + 'Ã'; 120 }); 121 122 sidePanelFontSlider.addEventListener('input', function () { 123 const v = parseInt(this.value, 10); 124 document.documentElement.style.setProperty('--side-panel-font-size', v + 'px'); 125 sidePanelFontReadout.textContent = v + 'px'; 126 }); 127 128 /* ================================================================ 129 4. Î ÎΥΣΠ130 ================================================================ */ 131 pauseToggleBtn.addEventListener('click', function () {
132 if (!ET.state.isPaused) { 133 /* ΠαÏÏη: ÏαγÏÎ½Î¿Ï Î¼Îµ Ïα λεÏÏά/Î´ÎµÏ ÏεÏÏλεÏÏα */ 134 const d = ET.now(); 135 ET.state.isPaused = true; 136 ET.state.pauseStartMs = Date.now(); 137 ET.state.pausedAtMinutes = ET.toMinutes(d.getHours(), d.getMinutes()); 138 ET.state.pausedAtSeconds = d.getSeconds(); 139 pauseBanner.classList.remove('hidden'); 140 pauseToggleBtn.textContent = 'â¶ Î£Ï Î½ÎÏεια'; 141 pauseToggleBtn.classList.add('paused'); 142 } else { 143 /* Resume: ÏÏ ÏÏÏÏÎµÏ Ïη ms ÏαÏÏÎ·Ï ÎºÎ±Î¹ εÏÎκÏαÏη με ÏÏÏÎ¿Î³Î³Ï Î»Î¿ÏοίηÏη */ 144 ET.state.pauseAccumulatedMs += Date.now() - ET.state.pauseStartMs; 145 const roundedMins = Math.round(ET.state.pauseAccumulatedMs / 60000); 146 const toAdd = roundedMins - ET.state.pauseAddedMins; 147 ET.state.isPaused = false; 148 if (toAdd > 0) { 149 ET.addTime(toAdd); 150 ET.state.pauseAddedMins = roundedMins; 151 } 152 pauseBanner.classList.add('hidden'); 153 pauseToggleBtn.textContent = '⸠ΠαÏÏη ÎνÏίÏÏÏοÏÎ·Ï ÎÎÏÏηÏηÏ'; 154 pauseToggleBtn.classList.remove('paused'); 155 } 156 }); 157 158 /* ================================================================ 159 5. ÎÎÎÎÎ ÎÎÎÎ 160 ================================================================ */ 161 $('endScreenToggle').addEventListener('change', function () { 162 ET.state.endScreenEnabled = this.checked; 163 ET.state.endScreenShown = false; /* reset ÏÏÏε να μÏοÏεί να ξαναεμÏανιÏÏεί */ 164 }); 165 166 endScreenOverlay.addEventListener('click', function () { 167 endScreenOverlay.classList.add('hidden'); 168 /* ΠαÏαμÎνει dismissed â Ïο resetNotifFlags() θα Ïο εÏαναÏÎÏει αν Î±Î»Î»Î¬Î¾Î¿Ï Î½ οι ÏÏÎµÏ */ 169 ET.state.endScreenShown = true; 170 }); 171 172 /* ================================================================ 173 6. ΠΡÎÎÎ ÎÎÎÎÎΣ 174 ================================================================ */ 175 document.querySelectorAll('.preset-btn').forEach(function (btn) { 176 btn.addEventListener('click', function () { 177 const duration = parseInt(this.dataset.duration, 10); 178 const departure = parseInt(this.dataset.departure, 10); 179 const d = ET.now(); 180 const nowMins = ET.toMinutes(d.getHours(), d.getMinutes()); 181 182 ET.els.startTime.value = ET.formatMinutes(nowMins); 183 ET.els.departureTime.value = ET.formatMinutes((nowMins + departure) % 1440); 184 ET.els.endTime.value = ET.formatMinutes((nowMins + duration) % 1440); 185 186 ET.syncFromInputs(); 187 closeMenu(); 188 }); 189 }); 190 191 /* ================================================================ 192 ZOOM ΣÎÎÎÎÎΣ 193 ================================================================ */ 194 var ZOOM_STEP = 0.1; 195 var ZOOM_MIN = 0.5; 196 var ZOOM_MAX = 2.0; 197 198 function applyUserZoom(z) { 199 z = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, Math.round(z * 10) / 10)); 200 window.examTimerUserZoom = z; 201 window.dispatchEvent(new Event('resize')); 202 if (zoomPctLabel) zoomPctLabel.textContent = Math.round(z * 100) + '%'; 203 } 204 205 if (zoomOutBtn) zoomOutBtn.addEventListener('click', function () { 206 applyUserZoom((window.examTimerUserZoom || 1) - ZOOM_STEP); 207 }); 208 if (zoomInBtn) zoomInBtn.addEventListener('click', function () { 209 applyUserZoom((window.examTimerUserZoom || 1) + ZOOM_STEP); 210 }); 211 212 /* ================================================================ 213 ÎÎÎÎÎÎÎ (Help Modal) 214 ================================================================ */ 215 function openHelp() { 216 helpBackdrop.classList.add('open'); 217 helpModalClose.focus(); 218 } 219 220 function closeHelp() { 221 helpBackdrop.classList.remove('open'); 222 helpBtn.focus(); 223 } 224 225 helpBtn.addEventListener('click', openHelp); 226 helpModalClose.addEventListener('click', closeHelp); 227 helpBackdrop.addEventListener('click', function (e) { 228 if (e.target === helpBackdrop) closeHelp(); 229 }); 230 document.addEventListener('keydown', function (e) { 231 if (e.key === 'Escape' && helpBackdrop.classList.contains('open')) closeHelp(); 232 }); 233 } 234 235 /* ÎκÏÎλεÏη μÏÎ»Î¹Ï ÏοÏÏÏθεί Ïο DOM */ 236 if (document.readyState === 'loading') { 237 document.addEventListener('DOMContentLoaded', init); 238 } else { 239 init(); 240 } 241 242})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.