Skip to content

Commit f77d847

Browse files
committed
✨ Add additional timer with settings
1 parent bb2a085 commit f77d847

File tree

6 files changed

+86
-51
lines changed

6 files changed

+86
-51
lines changed

index.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ <h3>Settings</h3>
5757
<input type="checkbox" id="play-sound" name="play-sound"/>
5858
</div>
5959

60+
<label for="overtime">Display overtime :</label>
61+
<div>
62+
<input type="checkbox" id="overtime" name="overtime"/>
63+
</div>
64+
6065
<label for="orientation">Orientation :</label>
6166
<div>
6267
<select id="orientation" name="orientation" disabled>

js/app.mjs

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const totalHeight = window.innerHeight;
1717
let elapsed = 0;
1818
let pause = true;
1919
const pauseChar = '⏸️';
20-
const playChar = '▶️';
20+
const playChar = '▶️';
2121

2222
let wakeLock = null;
2323

@@ -68,15 +68,19 @@ function startAnimation(startTime = performance.now(), settings) {
6868
} else if (progress < 1) {
6969
requestAnimationFrame(animate);
7070
} else {
71-
timer.textContent = "00:00";
72-
playBeep(settings);
73-
timer.classList.add("blinking"); // démarre le clignotement
74-
75-
// Arrête le clignotement après 5 secondes
76-
setTimeout(() => {
77-
timer.classList.remove("blinking");
78-
timer.style.opacity = "1";
79-
}, 5000);
71+
if (settings.overtime) {
72+
requestAnimationFrame(animate);
73+
} else {
74+
timer.textContent = "00:00";
75+
playBeep(settings);
76+
timer.classList.add("blinking"); // démarre le clignotement
77+
78+
// Arrête le clignotement après 5 secondes
79+
setTimeout(() => {
80+
timer.classList.remove("blinking");
81+
timer.style.opacity = "1";
82+
}, 5000);
83+
}
8084
}
8185
}
8286
requestAnimationFrame(animate);
@@ -132,8 +136,7 @@ function applySettings(settings, timer) {
132136
export function init() {
133137
const settings = initSettings({
134138
durationInSeconds: parseDuration(params.get("duration")), soundEnabled: params.get("sound") === "true", settingsModalElement: settingsModal, settingsFormElement: settingsForm
135-
})
136-
139+
});
137140
// Wakelock: réactiver si la page revient au premier plan
138141
document.addEventListener('visibilitychange', () => {
139142
if (wakeLock !== null && document.visibilityState === 'visible') {
@@ -145,7 +148,7 @@ export function init() {
145148
closeSettingsBtn.addEventListener('click', hideSettings);
146149
resetBtn.addEventListener('click', resetDefaultSettings);
147150
submitBtn.addEventListener('click', () => {
148-
submitSettings()
151+
submitSettings(settingsForm)
149152
applySettings(settings, timer);
150153
});
151154
startBtn.addEventListener('click', () => switchPause(settings));

js/settings.mjs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,22 @@ const defaultSettings = {
66
secondThreshold: 0.9,
77
thirdThreshold: 0.95,
88
soundEnabled: false,
9-
orientation: "upward"
9+
orientation: "upward",
10+
overtime: true,
1011
}
1112

1213
let settings;
1314
let settingsModal;
14-
let settingsForm;
1515

16-
function initSettings({ durationInSeconds, soundEnabled, settingsModalElement, settingsFormElement }) {
16+
function initSettings({
17+
durationInSeconds = defaultSettings.durationInSeconds,
18+
soundEnabled = defaultSettings.soundEnabled,
19+
settingsModalElement,
20+
settingsFormElement,
21+
}) {
1722
settingsModal = settingsModalElement;
18-
settingsForm = settingsFormElement;
1923
settings = { ...defaultSettings, durationInSeconds, soundEnabled }
24+
updateSettingsForm(settingsFormElement);
2025
return settings;
2126
}
2227

@@ -28,7 +33,7 @@ function hideSettings() {
2833
settingsModal.close();
2934
}
3035

31-
function submitSettings() {
36+
function submitSettings(settingsForm) {
3237
settings.durationInSeconds = Number(settingsForm["durationMinutes"].value * 60) + Number(settingsForm["durationSeconds"].value)
3338
settings.colorScheme = settingsForm["color-scheme"].value;
3439
settings.showTimer = settingsForm["show-timer"].checked;
@@ -37,12 +42,13 @@ function submitSettings() {
3742
settings.thirdThreshold = settingsForm["threshold3"].value / 100;
3843
settings.soundEnabled = settingsForm["play-sound"].checked;
3944
settings.orientation = settingsForm["orientation"].value;
45+
settings.overtime = settingsForm["overtime"].checked;
4046

4147
hideSettings();
4248
}
4349

4450

45-
function updateSettingsForm() {
51+
function updateSettingsForm(settingsForm) {
4652
// Apply the settings to the form so it reflects current settings
4753
settingsForm["durationMinutes"].value = Math.floor(settings.durationInSeconds / 60);
4854
settingsForm["durationSeconds"].value = settings.durationInSeconds % 60;
@@ -53,6 +59,7 @@ function updateSettingsForm() {
5359
settingsForm["threshold3"].value = settings.thirdThreshold * 100;
5460
settingsForm["play-sound"].checked = settings.soundEnabled;
5561
settingsForm["orientation"].value = settings.orientation;
62+
settingsForm["overtime"].checked = settings.overtime;
5663
}
5764

5865
function resetDefaultSettings() {

js/tests/settings.test.js

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,22 @@ import { describe, it, mock } from "node:test";
22
import assert from "node:assert";
33
import { initSettings, showSettings, hideSettings, submitSettings, updateSettingsForm } from "../settings.mjs";
44

5+
const settingsFormElementEmpty = {
6+
["color-scheme"]: { value: undefined },
7+
["threshold1"]: { value: undefined },
8+
["threshold2"]: { value: undefined },
9+
["threshold3"]: { value: undefined },
10+
durationMinutes: { value: undefined },
11+
durationSeconds: { value: undefined },
12+
orientation: { value: undefined },
13+
["show-timer"]: { checked: undefined },
14+
["play-sound"]: { checked: undefined },
15+
overtime: { checked: undefined },
16+
};
517

618
describe('initSettings', () => {
719
it(`Should create settings with given parameters`, () => {
8-
const settings = initSettings({ durationInSeconds: 20, soundEnabled: true });
20+
const settings = initSettings({ durationInSeconds: 20, soundEnabled: true, settingsFormElement: { ...settingsFormElementEmpty } });
921

1022
assert.deepEqual(settings, {
1123
colorScheme: 'zenika-colors',
@@ -16,6 +28,7 @@ describe('initSettings', () => {
1628
showTimer: true,
1729
soundEnabled: true,
1830
thirdThreshold: 0.95,
31+
overtime: true,
1932
});
2033
});
2134
});
@@ -24,7 +37,7 @@ describe('initSettings', () => {
2437
describe('showSettings', () => {
2538
it(`Should open the modal`, () => {
2639
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
27-
initSettings({ settingsModalElement });
40+
initSettings({ settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });
2841

2942
showSettings();
3043

@@ -36,7 +49,7 @@ describe('showSettings', () => {
3649
describe('hideSettings', () => {
3750
it(`Should close the modal`, () => {
3851
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
39-
initSettings({ settingsModalElement });
52+
initSettings({ settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });
4053

4154
hideSettings();
4255

@@ -57,22 +70,23 @@ describe('submitSettings', () => {
5770
orientation: { value: 'downward' },
5871
["show-timer"]: { checked: false },
5972
["play-sound"]: { checked: false },
73+
overtime: { checked: false },
6074
};
6175

6276
it(`Should close the modal`, () => {
6377
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
64-
initSettings({ settingsModalElement, settingsFormElement });
78+
initSettings({ settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });
6579

66-
submitSettings();
80+
submitSettings(settingsFormElement);
6781

6882
assert.equal(settingsModalElement.close.mock.callCount(), 1);
6983
});
7084

7185
it(`Should update the settings with the given form`, () => {
7286
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
73-
const settings = initSettings({ settingsModalElement, settingsFormElement });
87+
const settings = initSettings({ durationInSeconds: 61, settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });
7488

75-
submitSettings();
89+
submitSettings(settingsFormElement);
7690

7791
assert.deepEqual(settings, {
7892
colorScheme: 'zenika-other-colors',
@@ -83,29 +97,19 @@ describe('submitSettings', () => {
8397
orientation: 'downward',
8498
showTimer: false,
8599
soundEnabled: false,
100+
overtime: false,
86101
});
87102
});
88103
});
89104

90105

91106
describe('updateSettingsForm', () => {
92-
const settingsFormElement = {
93-
["color-scheme"]: { value: undefined },
94-
["threshold1"]: { value: undefined },
95-
["threshold2"]: { value: undefined },
96-
["threshold3"]: { value: undefined },
97-
durationMinutes: { value: undefined },
98-
durationSeconds: { value: undefined },
99-
orientation: { value: undefined },
100-
["show-timer"]: { checked: undefined },
101-
["play-sound"]: { checked: undefined },
102-
};
103-
104107
it(`Should update the form with the given settings`, () => {
105108
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
109+
const settingsFormElement = { ...settingsFormElementEmpty };
106110
initSettings({ settingsModalElement, settingsFormElement, soundEnabled: true, durationInSeconds: 123 });
107111

108-
updateSettingsForm();
112+
updateSettingsForm(settingsFormElement);
109113

110114
assert.deepEqual(settingsFormElement, {
111115
["color-scheme"]: { value: 'zenika-colors' },
@@ -117,6 +121,7 @@ describe('updateSettingsForm', () => {
117121
orientation: { value: 'upward' },
118122
["show-timer"]: { checked: true },
119123
["play-sound"]: { checked: true },
124+
overtime: { checked: true },
120125
});
121126
});
122127
});

js/tests/utils.test.js

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,31 @@ import assert from "node:assert";
33
import { updateTimer, updateBackground, parseDuration } from "../utils.mjs";
44

55
describe('Update timer', () => {
6-
[
7-
{ timer: '00:00', time: 0 },
8-
{ timer: '00:02', time: 2 },
9-
{ timer: '01:01', time: 61 },
10-
{ timer: '1440:01', time: 86401 }
11-
].forEach(({ timer, time }) => {
12-
it(`with ${time} displays ${timer}`, () => {
13-
assert.equal(updateTimer(time), timer);
14-
})
15-
})
6+
describe('When remaining time is greater than 0', () => {
7+
[
8+
{ timer: '00:00', time: 0 },
9+
{ timer: '00:02', time: 2 },
10+
{ timer: '01:01', time: 61 },
11+
{ timer: '1440:01', time: 86401 }
12+
].forEach(({ timer, time }) => {
13+
it(`with ${time} displays ${timer}`, () => {
14+
assert.equal(updateTimer(time), timer);
15+
})
16+
});
17+
});
18+
19+
describe('When remaining time is less than 0', () => {
20+
[
21+
{ timer: '00:00', time: -0 },
22+
{ timer: '+00:02', time: -2 },
23+
{ timer: '+01:01', time: -61 },
24+
{ timer: '+1440:01', time: -86401 }
25+
].forEach(({ timer, time }) => {
26+
it(`with ${time} displays ${timer}`, () => {
27+
assert.equal(updateTimer(time), timer);
28+
})
29+
});
30+
});
1631
});
1732

1833
describe('updateBackground', () => {

js/utils.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export function updateTimer(secRemaining) {
2-
const sec = Math.max(0, Math.ceil(secRemaining));
2+
const sec = Math.abs(Math.ceil(secRemaining));
33
const minutes = Math.floor(sec / 60);
44
const remainingSeconds = sec % 60;
55
// format into "mm:ss" padded with 0 if needed
6-
return `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
6+
return `${secRemaining < 0 ? '+' : ''}${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
77
}
88

99
export function updateBackground({ background, totalHeight, progress, settings }) {

0 commit comments

Comments
 (0)