PageSourceSearch

https://exetaseis.netlify.app/menu.js

js exetaseis.netlify.app collected 2026-10-03 10:42:56 UTC 9,884 bytes, 242 lines download raw bytes

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.