Skip to content

Commit bb2a085

Browse files
Merge pull request #33 from aceol/test-refactoring
♻️ Extract settings and add tests
2 parents e6ee8b2 + eaaddea commit bb2a085

File tree

6 files changed

+385
-154
lines changed

6 files changed

+385
-154
lines changed

js/app.mjs

Lines changed: 34 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// fetch params in url for quick settings
2-
import { updateTimer } from "./utils.mjs";
2+
import { updateBackground, updateTimer, parseDuration } from "./utils.mjs";
3+
import { showSettings, hideSettings, submitSettings, resetDefaultSettings, initSettings } from "./settings.mjs";
34

45
// Lecture du paramètre "duration" dans l'URL (en secondes)
56
const params = new URLSearchParams(globalThis.location.search);
@@ -18,128 +19,10 @@ let pause = true;
1819
const pauseChar = '⏸️';
1920
const playChar = '▶️';
2021

21-
const defaultSettings = {
22-
"durationInSeconds": 600,
23-
"showTimer": true,
24-
"colorScheme": "zenika-colors",
25-
"firstThreshold": 0.8,
26-
"secondThreshold": 0.9,
27-
"thirdThreshold": 0.95,
28-
"soundEnabled": false,
29-
"orientation": "upward"
30-
}
31-
32-
let settings = {
33-
"durationInSeconds": parseDuration(params.get("duration")),
34-
"showTimer": true,
35-
"colorScheme": "zenika-colors",
36-
"firstThreshold": 0.8,
37-
"secondThreshold": 0.9,
38-
"thirdThreshold": 0.95,
39-
"soundEnabled": params.get("sound") === "true",
40-
"orientation": "upward"
41-
}
42-
4322
let wakeLock = null;
4423

45-
46-
// ------------- Settings ----------------
47-
48-
function parseDuration(durationStr) {
49-
const DEFAULT = 600; // 10 minutes by default
50-
if (!durationStr) return DEFAULT;
51-
52-
durationStr = durationStr.toLowerCase().trim();
53-
let totalSeconds = 0;
54-
const regex = /(\d+)([ms:]?)/g;
55-
let match;
56-
let found = false;
57-
58-
while ((match = regex.exec(durationStr)) !== null) {
59-
found = true;
60-
const value = Number.parseInt(match[1], 10);
61-
const unit = match[2] || "s";
62-
if (unit === "m" || unit === ":") {
63-
totalSeconds += value * 60;
64-
} else {
65-
totalSeconds += value;
66-
}
67-
}
68-
69-
if (!found || totalSeconds <= 0) {
70-
console.warn(`Durée invalide ("${durationStr}"), utilisation de la valeur par défaut: ${DEFAULT}s`);
71-
return DEFAULT;
72-
}
73-
return totalSeconds;
74-
}
75-
76-
77-
function showSettings() {
78-
settingsModal.showModal();
79-
}
80-
81-
function hideSettings() {
82-
settingsModal.close();
83-
}
84-
85-
function submitSettings() {
86-
settings.durationInSeconds = Number(settingsForm["durationMinutes"].value * 60) + Number(settingsForm["durationSeconds"].value)
87-
settings.colorScheme = settingsForm["color-scheme"].value;
88-
settings.showTimer = settingsForm["show-timer"].checked;
89-
settings.firstThreshold = settingsForm["threshold1"].value / 100;
90-
settings.secondThreshold = settingsForm["threshold2"].value / 100;
91-
settings.thirdThreshold = settingsForm["threshold3"].value / 100;
92-
settings.soundEnabled = settingsForm["play-sound"].checked;
93-
settings.orientation = settingsForm["orientation"].value;
94-
95-
applySettings();
96-
hideSettings();
97-
}
98-
99-
function applySettings() {
100-
timer.textContent = updateTimer(settings.durationInSeconds);
101-
document.documentElement.className = settings.colorScheme;
102-
103-
if (settings.showTimer === false) {
104-
// maybe only hide the timer once it's started, but keep it visible until start to show the duration?
105-
timer.style.display = "none";
106-
} else {
107-
timer.style.display = "block";
108-
}
109-
110-
updateSettingsForm();
111-
112-
// Currently : apply changes to running timer. Maybe start the timer over?
113-
}
114-
115-
function updateSettingsForm() {
116-
// Apply the settings to the form so it reflects current settings
117-
settingsForm["durationMinutes"].value = Math.floor(settings.durationInSeconds / 60);
118-
settingsForm["durationSeconds"].value = settings.durationInSeconds % 60;
119-
settingsForm["show-timer"].checked = settings.showTimer;
120-
settingsForm["color-scheme"].value = settings.colorScheme;
121-
settingsForm["threshold1"].value = settings.firstThreshold * 100;
122-
settingsForm["threshold2"].value = settings.secondThreshold * 100;
123-
settingsForm["threshold3"].value = settings.thirdThreshold * 100;
124-
settingsForm["play-sound"].checked = settings.soundEnabled;
125-
settingsForm["orientation"].value = settings.orientation;
126-
}
127-
128-
function resetDefaultSettings() {
129-
settings = defaultSettings;
130-
updateSettingsForm();
131-
}
132-
13324
// ------------- Animation ----------------
134-
function getClassByProgress(p) {
135-
// p = percentage between 0 and 1
136-
if (p < settings.firstThreshold) return 'start';
137-
if (p < settings.secondThreshold) return 'critical';
138-
if (p < settings.thirdThreshold) return 'very-critical';
139-
return 'ending';
140-
}
141-
142-
function switchPause() {
25+
function switchPause(settings) {
14326
pause = !pause;
14427
if (pause) {
14528
displayStart();
@@ -148,7 +31,7 @@ function switchPause() {
14831
timer.classList.add("pause");
14932
timer.setAttribute('title', "Click to continue");
15033
} else {
151-
startAnimation(performance.now() - elapsed)
34+
startAnimation(performance.now() - elapsed, settings)
15235
timer.classList.remove("blinking");
15336
timer.classList.remove("pause");
15437
timer.title = "Click to pause";
@@ -165,31 +48,28 @@ function displayStart() {
16548
startBtn.title = 'start';
16649
}
16750

168-
function updateBackground(progress) {
169-
const currentHeight = Math.floor(totalHeight * progress);
170-
background.style.height = `${currentHeight}px`;
171-
background.classList = getClassByProgress(progress);
172-
}
17351

174-
function startAnimation(startTime = performance.now()) {
52+
53+
function startAnimation(startTime = performance.now(), settings) {
17554
requestWakeLock();
17655
displayPause();
56+
const { durationInSeconds } = settings;
17757

178-
function animate(time, durationInSeconds = settings.durationInSeconds) {
58+
function animate(time) {
17959
elapsed = time - startTime;
18060
const progress = Math.min(elapsed / (durationInSeconds * 1000), 1); // 0 → 1
18161
const remaining = durationInSeconds - (elapsed / 1000);
18262
timer.textContent = updateTimer(remaining);
18363

184-
updateBackground(progress)
64+
updateBackground({ background, totalHeight, progress, settings })
18565

18666
if (pause) {
18767
return;
18868
} else if (progress < 1) {
18969
requestAnimationFrame(animate);
19070
} else {
19171
timer.textContent = "00:00";
192-
playBeep();
72+
playBeep(settings);
19373
timer.classList.add("blinking"); // démarre le clignotement
19474

19575
// Arrête le clignotement après 5 secondes
@@ -216,7 +96,7 @@ async function requestWakeLock() {
21696
}
21797
}
21898

219-
function playBeep() {
99+
function playBeep(settings) {
220100
if (!settings.soundEnabled) return;
221101

222102
const AudioContext = window.AudioContext || window.webkitAudioContext;
@@ -236,23 +116,40 @@ function playBeep() {
236116
oscillator.stop(audioCtx.currentTime + 0.3); // play for 0.3s
237117
}
238118

119+
function applySettings(settings, timer) {
120+
timer.textContent = updateTimer(settings.durationInSeconds);
121+
document.documentElement.className = settings.colorScheme;
122+
123+
if (settings.showTimer === false) {
124+
// maybe only hide the timer once it's started, but keep it visible until start to show the duration?
125+
timer.style.display = "none";
126+
} else {
127+
timer.style.display = "block";
128+
}
129+
}
130+
239131

240132
export function init() {
133+
const settings = initSettings({
134+
durationInSeconds: parseDuration(params.get("duration")), soundEnabled: params.get("sound") === "true", settingsModalElement: settingsModal, settingsFormElement: settingsForm
135+
})
136+
241137
// Wakelock: réactiver si la page revient au premier plan
242138
document.addEventListener('visibilitychange', () => {
243139
if (wakeLock !== null && document.visibilityState === 'visible') {
244140
requestWakeLock();
245141
}
246142
});
247143

248-
249144
showSettingsBtn.addEventListener('click', showSettings);
250145
closeSettingsBtn.addEventListener('click', hideSettings);
251146
resetBtn.addEventListener('click', resetDefaultSettings);
252-
submitBtn.addEventListener('click', submitSettings);
253-
startBtn.addEventListener('click', switchPause);
254-
displayStart();
147+
submitBtn.addEventListener('click', () => {
148+
submitSettings()
149+
applySettings(settings, timer);
150+
});
151+
startBtn.addEventListener('click', () => switchPause(settings));
255152

256-
applySettings();
153+
displayStart();
154+
applySettings(settings, timer);
257155
}
258-

js/settings.mjs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const defaultSettings = {
2+
durationInSeconds: 600,
3+
showTimer: true,
4+
colorScheme: "zenika-colors",
5+
firstThreshold: 0.8,
6+
secondThreshold: 0.9,
7+
thirdThreshold: 0.95,
8+
soundEnabled: false,
9+
orientation: "upward"
10+
}
11+
12+
let settings;
13+
let settingsModal;
14+
let settingsForm;
15+
16+
function initSettings({ durationInSeconds, soundEnabled, settingsModalElement, settingsFormElement }) {
17+
settingsModal = settingsModalElement;
18+
settingsForm = settingsFormElement;
19+
settings = { ...defaultSettings, durationInSeconds, soundEnabled }
20+
return settings;
21+
}
22+
23+
function showSettings() {
24+
settingsModal.showModal();
25+
}
26+
27+
function hideSettings() {
28+
settingsModal.close();
29+
}
30+
31+
function submitSettings() {
32+
settings.durationInSeconds = Number(settingsForm["durationMinutes"].value * 60) + Number(settingsForm["durationSeconds"].value)
33+
settings.colorScheme = settingsForm["color-scheme"].value;
34+
settings.showTimer = settingsForm["show-timer"].checked;
35+
settings.firstThreshold = settingsForm["threshold1"].value / 100;
36+
settings.secondThreshold = settingsForm["threshold2"].value / 100;
37+
settings.thirdThreshold = settingsForm["threshold3"].value / 100;
38+
settings.soundEnabled = settingsForm["play-sound"].checked;
39+
settings.orientation = settingsForm["orientation"].value;
40+
41+
hideSettings();
42+
}
43+
44+
45+
function updateSettingsForm() {
46+
// Apply the settings to the form so it reflects current settings
47+
settingsForm["durationMinutes"].value = Math.floor(settings.durationInSeconds / 60);
48+
settingsForm["durationSeconds"].value = settings.durationInSeconds % 60;
49+
settingsForm["show-timer"].checked = settings.showTimer;
50+
settingsForm["color-scheme"].value = settings.colorScheme;
51+
settingsForm["threshold1"].value = settings.firstThreshold * 100;
52+
settingsForm["threshold2"].value = settings.secondThreshold * 100;
53+
settingsForm["threshold3"].value = settings.thirdThreshold * 100;
54+
settingsForm["play-sound"].checked = settings.soundEnabled;
55+
settingsForm["orientation"].value = settings.orientation;
56+
}
57+
58+
function resetDefaultSettings() {
59+
settings = defaultSettings;
60+
updateSettingsForm();
61+
}
62+
63+
export { showSettings, hideSettings, submitSettings, updateSettingsForm, resetDefaultSettings, initSettings }

js/tests/app.test.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)