Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ <h3>Settings</h3>
<input type="checkbox" id="play-sound" name="play-sound"/>
</div>

<label for="overtime">Display overtime :</label>
<div>
<input type="checkbox" id="overtime" name="overtime"/>
</div>

<label for="orientation">Orientation :</label>
<div>
<select id="orientation" name="orientation" disabled>
Expand Down
29 changes: 16 additions & 13 deletions js/app.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const totalHeight = window.innerHeight;
let elapsed = 0;
let pause = true;
const pauseChar = '⏸️';
const playChar = '▶️';
const playChar = '▶️';

let wakeLock = null;

Expand Down Expand Up @@ -68,15 +68,19 @@ function startAnimation(startTime = performance.now(), settings) {
} else if (progress < 1) {
requestAnimationFrame(animate);
} else {
timer.textContent = "00:00";
playBeep(settings);
timer.classList.add("blinking"); // démarre le clignotement

// Arrête le clignotement après 5 secondes
setTimeout(() => {
timer.classList.remove("blinking");
timer.style.opacity = "1";
}, 5000);
if (settings.overtime) {
requestAnimationFrame(animate);
} else {
timer.textContent = "00:00";
playBeep(settings);
timer.classList.add("blinking"); // démarre le clignotement

// Arrête le clignotement après 5 secondes
setTimeout(() => {
timer.classList.remove("blinking");
timer.style.opacity = "1";
}, 5000);
}
}
}
requestAnimationFrame(animate);
Expand Down Expand Up @@ -132,8 +136,7 @@ function applySettings(settings, timer) {
export function init() {
const settings = initSettings({
durationInSeconds: parseDuration(params.get("duration")), soundEnabled: params.get("sound") === "true", settingsModalElement: settingsModal, settingsFormElement: settingsForm
})

});
// Wakelock: réactiver si la page revient au premier plan
document.addEventListener('visibilitychange', () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
Expand All @@ -145,7 +148,7 @@ export function init() {
closeSettingsBtn.addEventListener('click', hideSettings);
resetBtn.addEventListener('click', resetDefaultSettings);
submitBtn.addEventListener('click', () => {
submitSettings()
submitSettings(settingsForm)
applySettings(settings, timer);
});
startBtn.addEventListener('click', () => switchPause(settings));
Expand Down
19 changes: 13 additions & 6 deletions js/settings.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@ const defaultSettings = {
secondThreshold: 0.9,
thirdThreshold: 0.95,
soundEnabled: false,
orientation: "upward"
orientation: "upward",
overtime: true,
}

let settings;
let settingsModal;
let settingsForm;

function initSettings({ durationInSeconds, soundEnabled, settingsModalElement, settingsFormElement }) {
function initSettings({
durationInSeconds = defaultSettings.durationInSeconds,
soundEnabled = defaultSettings.soundEnabled,
settingsModalElement,
settingsFormElement,
}) {
settingsModal = settingsModalElement;
settingsForm = settingsFormElement;
settings = { ...defaultSettings, durationInSeconds, soundEnabled }
updateSettingsForm(settingsFormElement);
return settings;
}

Expand All @@ -28,7 +33,7 @@ function hideSettings() {
settingsModal.close();
}

function submitSettings() {
function submitSettings(settingsForm) {
settings.durationInSeconds = Number(settingsForm["durationMinutes"].value * 60) + Number(settingsForm["durationSeconds"].value)
settings.colorScheme = settingsForm["color-scheme"].value;
settings.showTimer = settingsForm["show-timer"].checked;
Expand All @@ -37,12 +42,13 @@ function submitSettings() {
settings.thirdThreshold = settingsForm["threshold3"].value / 100;
settings.soundEnabled = settingsForm["play-sound"].checked;
settings.orientation = settingsForm["orientation"].value;
settings.overtime = settingsForm["overtime"].checked;

hideSettings();
}


function updateSettingsForm() {
function updateSettingsForm(settingsForm) {
// Apply the settings to the form so it reflects current settings
settingsForm["durationMinutes"].value = Math.floor(settings.durationInSeconds / 60);
settingsForm["durationSeconds"].value = settings.durationInSeconds % 60;
Expand All @@ -53,6 +59,7 @@ function updateSettingsForm() {
settingsForm["threshold3"].value = settings.thirdThreshold * 100;
settingsForm["play-sound"].checked = settings.soundEnabled;
settingsForm["orientation"].value = settings.orientation;
settingsForm["overtime"].checked = settings.overtime;
}

function resetDefaultSettings() {
Expand Down
45 changes: 25 additions & 20 deletions js/tests/settings.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ import { describe, it, mock } from "node:test";
import assert from "node:assert";
import { initSettings, showSettings, hideSettings, submitSettings, updateSettingsForm } from "../settings.mjs";

const settingsFormElementEmpty = {
["color-scheme"]: { value: undefined },
["threshold1"]: { value: undefined },
["threshold2"]: { value: undefined },
["threshold3"]: { value: undefined },
durationMinutes: { value: undefined },
durationSeconds: { value: undefined },
orientation: { value: undefined },
["show-timer"]: { checked: undefined },
["play-sound"]: { checked: undefined },
overtime: { checked: undefined },
};

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

assert.deepEqual(settings, {
colorScheme: 'zenika-colors',
Expand All @@ -16,6 +28,7 @@ describe('initSettings', () => {
showTimer: true,
soundEnabled: true,
thirdThreshold: 0.95,
overtime: true,
});
});
});
Expand All @@ -24,7 +37,7 @@ describe('initSettings', () => {
describe('showSettings', () => {
it(`Should open the modal`, () => {
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
initSettings({ settingsModalElement });
initSettings({ settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });

showSettings();

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

hideSettings();

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

it(`Should close the modal`, () => {
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
initSettings({ settingsModalElement, settingsFormElement });
initSettings({ settingsModalElement, settingsFormElement: { ...settingsFormElementEmpty } });

submitSettings();
submitSettings(settingsFormElement);

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

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

submitSettings();
submitSettings(settingsFormElement);

assert.deepEqual(settings, {
colorScheme: 'zenika-other-colors',
Expand All @@ -83,29 +97,19 @@ describe('submitSettings', () => {
orientation: 'downward',
showTimer: false,
soundEnabled: false,
overtime: false,
});
});
});


describe('updateSettingsForm', () => {
const settingsFormElement = {
["color-scheme"]: { value: undefined },
["threshold1"]: { value: undefined },
["threshold2"]: { value: undefined },
["threshold3"]: { value: undefined },
durationMinutes: { value: undefined },
durationSeconds: { value: undefined },
orientation: { value: undefined },
["show-timer"]: { checked: undefined },
["play-sound"]: { checked: undefined },
};

it(`Should update the form with the given settings`, () => {
const settingsModalElement = { showModal: mock.fn(), close: mock.fn() };
const settingsFormElement = { ...settingsFormElementEmpty };
initSettings({ settingsModalElement, settingsFormElement, soundEnabled: true, durationInSeconds: 123 });

updateSettingsForm();
updateSettingsForm(settingsFormElement);

assert.deepEqual(settingsFormElement, {
["color-scheme"]: { value: 'zenika-colors' },
Expand All @@ -117,6 +121,7 @@ describe('updateSettingsForm', () => {
orientation: { value: 'upward' },
["show-timer"]: { checked: true },
["play-sound"]: { checked: true },
overtime: { checked: true },
});
});
});
35 changes: 25 additions & 10 deletions js/tests/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@ import assert from "node:assert";
import { updateTimer, updateBackground, parseDuration } from "../utils.mjs";

describe('Update timer', () => {
[
{ timer: '00:00', time: 0 },
{ timer: '00:02', time: 2 },
{ timer: '01:01', time: 61 },
{ timer: '1440:01', time: 86401 }
].forEach(({ timer, time }) => {
it(`with ${time} displays ${timer}`, () => {
assert.equal(updateTimer(time), timer);
})
})
describe('When remaining time is greater than 0', () => {
[
{ timer: '00:00', time: 0 },
{ timer: '00:02', time: 2 },
{ timer: '01:01', time: 61 },
{ timer: '1440:01', time: 86401 }
].forEach(({ timer, time }) => {
it(`with ${time} displays ${timer}`, () => {
assert.equal(updateTimer(time), timer);
})
});
});

describe('When remaining time is less than 0', () => {
[
{ timer: '00:00', time: -0 },
{ timer: '+00:02', time: -2 },
{ timer: '+01:01', time: -61 },
{ timer: '+1440:01', time: -86401 }
].forEach(({ timer, time }) => {
it(`with ${time} displays ${timer}`, () => {
assert.equal(updateTimer(time), timer);
})
});
});
});

describe('updateBackground', () => {
Expand Down
4 changes: 2 additions & 2 deletions js/utils.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export function updateTimer(secRemaining) {
const sec = Math.max(0, Math.ceil(secRemaining));
const sec = Math.abs(Math.ceil(secRemaining));
const minutes = Math.floor(sec / 60);
const remainingSeconds = sec % 60;
// format into "mm:ss" padded with 0 if needed
return `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
return `${secRemaining < 0 ? '+' : ''}${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
}

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