diff --git a/asset/javascript/TimeController.js b/asset/javascript/TimeController.js index 931809b..52acf4e 100644 --- a/asset/javascript/TimeController.js +++ b/asset/javascript/TimeController.js @@ -101,6 +101,13 @@ function TimerController(reference) { function bindFullscreenEvents() { document.addEventListener('fullscreenchange', handleButtonFullscreenChange); + document.addEventListener('visibilitychange', handleVisibilityChange); + } + + function handleVisibilityChange() { + if (document.hidden) return; + + updatePageTitle(); } function validateInput(input, maxValue) { @@ -161,6 +168,8 @@ function TimerController(reference) { if (!canStart) return; + reference.classList.remove('inverted'); + countdownContainerReference.classList.remove('inverted'); startButton.hideElement(); pauseButton.showElement(); stopButton.showElement(); @@ -177,27 +186,28 @@ function TimerController(reference) { TimerStatus.isCountdown(lastTimerStatus) || TimerStatus.isPaused(lastTimerStatus); - if (canStart) { - let seconds = getInputsValueAsSeconds(); - seconds--; - if (seconds <= 10) { - lastTimerStatus = TimerStatus.COUNTDOWN; - playCountdownSound(); + if (!canStart) { + clearInterval(timerIntervalId); + return; + } - if (!preventOpenCountdown) executeCountdown(seconds); - } + let seconds = getInputsValueAsSeconds(); + seconds--; + if (seconds <= 10) { + lastTimerStatus = TimerStatus.COUNTDOWN; + playCountdownSound(); - setInputValues(seconds); + if (!preventOpenCountdown) executeCountdown(seconds); + } - if (seconds == 0) { - preventOpenCountdown = false; - lastTimerStatus = TimerStatus.STOPPED; + setInputValues(seconds); - showDefaultButtons(); - playStopSound(); - clearInterval(timerIntervalId); - } - } else { + if (seconds == 0) { + preventOpenCountdown = false; + lastTimerStatus = TimerStatus.STOPPED; + + showDefaultButtons(); + playStopSound(); clearInterval(timerIntervalId); } }, DEFAULT_INTERVAL); @@ -220,14 +230,15 @@ function TimerController(reference) { const fade = setInterval(() => { if (stopSound.volume > 0.05) { stopSound.volume -= 0.05; - } else { - stopSound.volume = 0; - stopSound.pause(); - stopSound.currentTime = 0; - clearInterval(fade); - setInputValues(DEFAULT_SECONDS); - preventOpenCountdown = false; + return; } + + stopSound.volume = 0; + stopSound.pause(); + stopSound.currentTime = 0; + clearInterval(fade); + setInputValues(DEFAULT_SECONDS); + preventOpenCountdown = false; }, 200); }, 3000); } @@ -236,14 +247,18 @@ function TimerController(reference) { countdownContainerReference.showElement(); countdownNumber.textContent = seconds; + countdownContainerReference.classList.toggle('even'); + countdownContainerReference.classList.toggle('odd'); + if (seconds % 2 === 0) { - countdownContainerReference.classList.add('even'); - countdownContainerReference.classList.remove('odd'); + reference.classList.add('inverted'); + countdownContainerReference.classList.add('inverted'); + return; } - countdownContainerReference.classList.add('odd'); - countdownContainerReference.classList.remove('even'); + reference.classList.remove('inverted'); + countdownContainerReference.classList.remove('inverted'); } function stop() { @@ -251,24 +266,29 @@ function TimerController(reference) { preventOpenCountdown = false; lastTimerStatus = TimerStatus.STOPPED; + reference.classList.remove('inverted'); + countdownContainerReference.classList.remove('inverted'); showDefaultButtons(); setInputValues(DEFAULT_SECONDS); clearInterval(timerIntervalId); } function pause() { - if (lastTimerStatus === TimerStatus.RUNNING || lastTimerStatus === TimerStatus.COUNTDOWN) { - lastTimerStatus = TimerStatus.PAUSED; - startButton.showElement(); - pauseButton.hideElement(); - clearInterval(timerIntervalId); - } + const shouldNotPause = lastTimerStatus !== TimerStatus.RUNNING && lastTimerStatus !== TimerStatus.COUNTDOWN; + if (shouldNotPause) return; + + lastTimerStatus = TimerStatus.PAUSED; + + startButton.showElement(); + pauseButton.hideElement(); + + clearInterval(timerIntervalId); } function isInFullscreen() { - return !!document.fullscreenElement + return !!document.fullscreenElement; } - + function handleButtonFullscreenChange() { if (isInFullscreen()) { exitFullscreenButton.showElement(); @@ -291,6 +311,8 @@ function TimerController(reference) { function closeCountdownContainer() { preventOpenCountdown = true; + reference.classList.remove('inverted'); + countdownContainerReference.classList.remove('inverted'); countdownContainerReference.hideElement(); countdownContainerReference.classList.remove('even', 'odd'); countdownNumber.textContent = ''; @@ -347,6 +369,13 @@ function TimerController(reference) { hourInput.value = formatTimeUnit(hours); minuteInput.value = formatTimeUnit(minutes); secondInput.value = formatTimeUnit(seconds); + + updatePageTitle(); + } + + function updatePageTitle() { + const { seconds, minutes, hours } = getInputValues(); + document.title = `${formatTimeUnit(hours)}:${formatTimeUnit(minutes)}:${formatTimeUnit(seconds)} - Timer `; } init(); @@ -358,4 +387,4 @@ document.addEventListener('DOMContentLoaded', function () { window.timerController = timerController; }); -export default TimerController; \ No newline at end of file +export default TimerController; diff --git a/asset/javascript/__tests__/TimerIntegration.test.js b/asset/javascript/__tests__/TimerIntegration.test.js index 268405d..3938cf6 100644 --- a/asset/javascript/__tests__/TimerIntegration.test.js +++ b/asset/javascript/__tests__/TimerIntegration.test.js @@ -8,6 +8,12 @@ describe('TimerController Integration', () => { beforeEach(() => { mockReference = { + classList: { + add: jest.fn(), + remove: jest.fn(), + contains: jest.fn(), + toggle: jest.fn(), + }, querySelector: jest.fn(selector => { if (selector === '.js-stopwatch-action-buttons') { return mockElement(); @@ -25,21 +31,25 @@ describe('TimerController Integration', () => { it('deve iniciar, pausar e parar o timer corretamente', () => { const timerController = new TimerController(mockReference); - - const startButton = mockReference.querySelector('.js-stopwatch-action-buttons').querySelector('.js-start-button'); + + const startButton = mockReference + .querySelector('.js-stopwatch-action-buttons') + .querySelector('.js-start-button'); expect(startButton.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)); - + const startHandler = startButton.addEventListener.mock.calls[0][1]; startHandler(); - + expect(startButton.hideElement).toHaveBeenCalled(); - - const pauseButton = mockReference.querySelector('.js-stopwatch-action-buttons').querySelector('.js-pause-button'); + + const pauseButton = mockReference + .querySelector('.js-stopwatch-action-buttons') + .querySelector('.js-pause-button'); expect(pauseButton.addEventListener).toHaveBeenCalledWith('click', expect.any(Function)); - + const pauseHandler = pauseButton.addEventListener.mock.calls[0][1]; pauseHandler(); - + expect(pauseButton.hideElement).toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/asset/style/timer.css b/asset/style/timer.css index 17a8128..436d4d3 100644 --- a/asset/style/timer.css +++ b/asset/style/timer.css @@ -1,137 +1,158 @@ -@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap'); :root { - --primary-bg-color: #242424; - --primary-text-color: #46ffbe; - --secondary-bg-color: #46ffbe; - --secondary-text-color: #444444; + --primary-bg-color: #242424; + --primary-text-color: #46ffbe; + --secondary-bg-color: #46ffbe; + --secondary-text-color: #444444; } * { - font-family: "Orbitron", sans-serif; - box-sizing: border-box; + font-family: 'Orbitron', sans-serif; + box-sizing: border-box; } body, html { - background-color: var(--primary-bg-color); - color: var(--primary-text-color); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 48px; - margin: 0; - height: 100dvh; - width: 100dvw; -} -body::-moz-selection, html::-moz-selection { - background: var(--secondary-bg-color); - color: var(--secondary-text-color); + background-color: var(--primary-bg-color); + color: var(--primary-text-color); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 48px; + margin: 0; + height: 100dvh; + width: 100dvw; +} +body::-moz-selection, +html::-moz-selection { + background: var(--secondary-bg-color); + color: var(--secondary-text-color); } body::selection, html::selection { - background: var(--secondary-bg-color); - color: var(--secondary-text-color); + background: var(--secondary-bg-color); + color: var(--secondary-text-color); } body ::-moz-selection, html ::-moz-selection { - background: var(--secondary-bg-color); - color: var(--secondary-text-color); + background: var(--secondary-bg-color); + color: var(--secondary-text-color); +} +body.inverted, +html.inverted { + background-color: var(--primary-text-color); + color: var(--primary-bg-color); +} +body.inverted .input-stopwatch-container input, +html.inverted .input-stopwatch-container input { + color: var(--primary-bg-color); } .countdown-container { - width: 100%; - height: 100%; - position: absolute; - background: var(--primary-bg-color); - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - transition: all 0.5s ease; + width: 100%; + height: 100%; + position: absolute; + background: var(--primary-bg-color); + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + transition: all 0.5s ease; } .countdown-container .countdown-number { - font-size: 202px; - transition: all 0.5s ease; + font-size: 202px; + transition: all 0.5s ease; } .countdown-container.odd { - background: var(--secondary-bg-color); - color: var(--secondary-text-color); + background: var(--secondary-bg-color); + color: var(--secondary-text-color); } .countdown-container.even { - background: var(--primary-bg-color); - color: var(--primary-text-color); + background: var(--primary-bg-color); + color: var(--primary-text-color); +} +.countdown-container.inverted { + background: var(--primary-text-color); + color: var(--primary-bg-color); +} +.countdown-container.inverted .action-button-container button { + border-color: var(--primary-bg-color); } .input-stopwatch-container { - width: 100%; - display: flex; - justify-content: center; - transition: all 0.5s ease; - font-size: 112px; + width: 100%; + display: flex; + justify-content: center; + transition: all 0.5s ease; + font-size: 112px; } @media screen and (max-width: 768px) { - .input-stopwatch-container { - font-size: 56px; - } + .input-stopwatch-container { + font-size: 56px; + } } .input-stopwatch-container input { - width: auto; - min-width: 1px; - max-width: 2ch; - margin: 0; - padding: 0; - color: var(--primary-text-color); - border: none; - background: none; - outline: none; - text-align: center; - font-size: inherit; - border-radius: 5px; -} -.input-stopwatch-container input::-webkit-outer-spin-button, .input-stopwatch-container input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -.input-stopwatch-container input[type=number] { - appearance: none; - -webkit-appearance: none; - -moz-appearance: textfield; + width: auto; + min-width: 1px; + max-width: 2ch; + margin: 0; + padding: 0; + color: var(--primary-text-color); + border: none; + background: none; + outline: none; + text-align: center; + font-size: inherit; + border-radius: 5px; +} +.input-stopwatch-container input::-webkit-outer-spin-button, +.input-stopwatch-container input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +.input-stopwatch-container input[type='number'] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: textfield; } .action-button-container { - display: flex; - flex-direction: row; - gap: 32px; + display: flex; + flex-direction: row; + gap: 32px; } .action-button-container button { - display: flex; - align-items: center; - justify-content: center; - padding: 0; - height: 32px; - width: 32px; - background-color: transparent; - border: 1px solid white; - border-radius: 99px; - color: inherit; - cursor: pointer; - transition: all 0.5s ease; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + height: 32px; + width: 32px; + background-color: transparent; + border: 1px solid white; + border-radius: 99px; + color: inherit; + cursor: pointer; + transition: all 0.5s ease; } .action-button-container button img { - max-width: 100%; - max-height: 100%; - display: block; + max-width: 100%; + max-height: 100%; + display: block; } .action-button-container button:hover { - background-color: rgba(255, 255, 255, 0.15); + background-color: rgba(255, 255, 255, 0.15); } -.action-button-container button:active, .action-button-container button:focus, .action-button-container button:focus-visible, .action-button-container button:focus-within { - box-shadow: 0 0 10px var(--secondary-bg-color); - background-color: rgba(255, 255, 255, 0.15); - outline: none; +.action-button-container button:active, +.action-button-container button:focus, +.action-button-container button:focus-visible, +.action-button-container button:focus-within { + box-shadow: 0 0 10px var(--secondary-bg-color); + background-color: rgba(255, 255, 255, 0.15); + outline: none; } .hide { - display: none !important; -}/*# sourceMappingURL=timer.css.map */ \ No newline at end of file + display: none !important; +} /*# sourceMappingURL=timer.css.map */ diff --git a/asset/style/timer.scss b/asset/style/timer.scss index f773877..560fdb7 100644 --- a/asset/style/timer.scss +++ b/asset/style/timer.scss @@ -1,4 +1,4 @@ -@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap"); +@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap'); :root { --primary-bg-color: #242424; @@ -8,7 +8,7 @@ } * { - font-family: "Orbitron", sans-serif; + font-family: 'Orbitron', sans-serif; box-sizing: border-box; } @@ -34,6 +34,15 @@ html { background: var(--secondary-bg-color); color: var(--secondary-text-color); } + + &.inverted { + background-color: var(--primary-text-color); + color: var(--primary-bg-color); + + .input-stopwatch-container input { + color: var(--primary-bg-color); + } + } } .countdown-container { @@ -61,6 +70,15 @@ html { background: var(--primary-bg-color); color: var(--primary-text-color); } + + &.inverted { + background: var(--primary-text-color); + color: var(--primary-bg-color); + + .action-button-container button { + border-color: var(--primary-bg-color); + } + } } .input-stopwatch-container { @@ -94,7 +112,7 @@ html { margin: 0; } - &[type="number"] { + &[type='number'] { appearance: none; -webkit-appearance: none; -moz-appearance: textfield; diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index a25e61e..0000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ['@babel/preset-env'], -}; \ No newline at end of file diff --git a/package.json b/package.json index 2d78375..b4a5906 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "scripts": { "dev": "parcel index.html", - "build": "parcel build index.html --public-url ./", + "build": "parcel build index.html --no-source-maps --public-url ./", "format": "prettier --write \"**/*.js\"", "test": "jest" }, @@ -22,9 +22,28 @@ }, "jest": { "testEnvironment": "jsdom", + "transform": { + "^.+\\.js$": [ + "babel-jest", + { + "presets": [ + "@babel/preset-env" + ] + } + ] + }, "moduleNameMapper": { "^.+\\.(wav|mp3)$": "/asset/javascript/__mocks__/fileMock.js" }, - "setupFiles": ["/asset/javascript/__mocks__/audioMock.js"] + "setupFiles": [ + "/asset/javascript/__mocks__/audioMock.js" + ] + }, + "targets": { + "default": { + "distDir": "./dist", + "optimize": true, + "scopeHoist": true + } } }