From ff2bf5ab403a36b6ee5c1557dceb9c4ffa505e01 Mon Sep 17 00:00:00 2001 From: davidy838 Date: Mon, 29 Jan 2024 21:09:43 -0800 Subject: [PATCH 1/3] stopwatch and css --- src/App.css | 85 +++++++++++------------------------ src/StopWatch.tsx | 111 +++++++++++++++++----------------------------- 2 files changed, 68 insertions(+), 128 deletions(-) diff --git a/src/App.css b/src/App.css index e5deef95..83ec1edd 100644 --- a/src/App.css +++ b/src/App.css @@ -1,71 +1,40 @@ -html, body, #root, .stopwatch, .stopwatch-content, .stopwatch-buttons { - height: -webkit-fill-available; -} - body { - background-color: #f1f1f1; - margin: 0px; -} - -.stopwatch-title { - background-color: #303030; - margin: 0px; - color: white; - padding-left: 16px; - padding: 10px 0px 10px 16px; -} - -.stopwatch-content { - display: flex; + margin: 0; + background-color: lightgreen; } -.stopwatch-buttons { +.stopwatch-container { + height: 100vh; display: flex; flex-direction: column; - background-color: #ebebeb; - padding: 16px 12px; - width: 200px; -} - -.stopwatch-buttons button:focus { - outline: none; - border: 2px solid #000000; + justify-content: center; + align-items: center; } -.stopwatch-buttons button { - margin: 7px 0px; - background-color: #fafafa; - border: 0px solid #fafafa; - text-align: left; - border-radius: 0.5rem; - padding: 7px 0px 7px 15px; - box-shadow: 0.5px 0.5px gray; -} - -.stopwatch-time { - margin-left: auto; - margin-right: auto; - margin-top: 20px; - padding: 50px; - background-color: #ffffff; - height: fit-content; - border-radius: 0.75rem; - width: 50%; - text-align: -webkit-center; - box-shadow: 0.5px 0.5px gray; +.timer-display { + display: flex; + color: #ccc; } -.stopwatch-time p { - font-size: xxx-large; +.timer-display p { + margin: 0; + font-size: 4rem; + letter-spacing: 2px; } -.stopwatch-laptimes ul { - list-style: none; - padding: 0px; +.timer-display span { + font-size: 3rem; + position: relative; + top: 8px; + letter-spacing: 2px; } -.stopwatch-laptimes li { - padding: 10px 0px; - border-bottom: 1px solid #ebebeb; - font-size: x-large; -} \ No newline at end of file +.stopwatch-controls-container button { + margin: 15px; + border: none; + background-color: #e5ad3d; + padding: 3px 12px; + font-size: 1.3rem; + font-weight: 400; + cursor: pointer; +} \ No newline at end of file diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx index 2e06473c..3897e086 100644 --- a/src/StopWatch.tsx +++ b/src/StopWatch.tsx @@ -1,77 +1,48 @@ -import React, { useState, useEffect, useCallback } from 'react' -import StopWatchButton from './StopWatchButton' +import React, { useEffect, useState } from 'react'; +import './css/App.css'; -// Function to format the time. This is necessary since both the time and lap times need to be formatted -export function formatTime(time: number): string { - // Format the time in mm:ss:ms. Display hours only if reached - const hours = Math.floor(time / 360000); - const minutes = Math.floor((time % 360000) / 6000); - const seconds = Math.floor((time % 6000) / 100); - const milliseconds = time % 100; - // Format the minutes, seconds, and milliseconds to be two digits - const formattedMinutes = minutes.toString().padStart(2, '0'); - const formattedSeconds = seconds.toString().padStart(2, '0'); - const formattedMilliseconds = milliseconds.toString().padStart(2, '0'); - // If stopwatch reaches at least an hour, display the hours - if (hours > 0) { - const formattedHours = hours.toString().padStart(2, '0'); - return `${formattedHours}:${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`; - } - // Combine the values into a string - const formattedTime = `${formattedMinutes}:${formattedSeconds}:${formattedMilliseconds}`; - return formattedTime; -} +export default function StopWatch(props: any) { + const [time, setTime] = useState(0); + const [timeDisplay, setTimeDisplay] = useState>([]); + const [intervalNumber, setIntervalNumber] = useState(0); -export default function StopWatch() { - // State to track the time, whether the timer is on/off, and the lap times - const [time, setTime] = useState(0); - const [timerOn, setTimerOn] = useState(false); - const [lapTimes, setLapTimes] = useState([]); + const convertTimeToDisplay = (time: number): (number | string)[] => { + const formatTime = (value: number): string => (value < 10 ? `0${value}` : `${value}`); - // Stops the timer, resets the time, and clears the lap times. useCallback is used to prevent unnecessary re-renders - const handleReset = useCallback(() => { - setTimerOn(false); - setTime(0); - setLapTimes([]); - }, []); + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time - hours * 3600) / 60); + const seconds = time - minutes * 60 - hours * 3600; - // Every time timerOn changes, we start or stop the timer - // useEffect is necessary since setInterval changes the state and we don't want to create an infinite loop - useEffect(() => { - let interval: ReturnType | null = null; + return [formatTime(hours), formatTime(minutes), formatTime(seconds)]; + }; - if (timerOn) { - interval = setInterval(() => setTime(time => time + 1), 10) - } + useEffect(() => { + const handleInterval = () => { + setTime((prevTime) => prevTime + 1); + }; - return () => {clearInterval(interval)} // Clears the interval when the component unmounts or timerOn changes - }, [timerOn]) + if (props.isRunning) { + const interval = setInterval(handleInterval, 1000); + setIntervalNumber(interval); + } else { + clearInterval(intervalNumber); + } - return( -
-

StopWatch

-
-
- setTimerOn(true)}> - setTimerOn(false)}> - setLapTimes([...lapTimes, time])} timerOn={timerOn} lapTimes={lapTimes}> - -
-
-

{formatTime(time)}

- {/* Display the numbered lap times */} - {lapTimes.length > 0 && ( -
-

Lap times

-
    - {lapTimes.map((lapTime, index) => { - return
  • {(index + 1)+'.'} {formatTime(lapTime)}
  • - })} -
-
- )} -
-
-
- ) -} \ No newline at end of file + if (props.reset) { + clearInterval(intervalNumber); + setTime(0); + } + + setTimeDisplay(convertTimeToDisplay(time)); + }, [props.isRunning, props.reset, time, intervalNumber]); + + return ( +
+

{timeDisplay[0]}

+ : +

{timeDisplay[1]}

+ : +

{timeDisplay[2]}

+
+ ); +} From cab2c4070c7c9e9d28a478e471048394caef3eab Mon Sep 17 00:00:00 2001 From: davidy838 Date: Mon, 29 Jan 2024 21:59:34 -0800 Subject: [PATCH 2/3] app.js --- src/App.tsx | 29 ++++++++++++++--- src/StopWatchButton.tsx | 67 +++++++++----------------------------- src/type/InterfaceSWBP.tsx | 4 +++ src/type/InterfaceSWT | 4 +++ 4 files changed, 47 insertions(+), 57 deletions(-) create mode 100644 src/type/InterfaceSWBP.tsx create mode 100644 src/type/InterfaceSWT diff --git a/src/App.tsx b/src/App.tsx index 8c90fc53..971810a3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,28 @@ -import React from 'react' -import './App.css' +import React, { useState } from 'react' import StopWatch from './StopWatch' +import './css/App.css' +import StopWatchButton from './StopWatchButton' -export default function App() { + export default function App() { + const [isRunning, setIsRunning] = useState(false); + const [reset, setReset] = useState(false); + + const handleOnStart = () => { + setIsRunning(true); + } + + const handleOnStop = () => { + setIsRunning(false); + } + + const handleReset = () => { + setReset(true); + } return( - +
+ + + +
) -} \ No newline at end of file +} diff --git a/src/StopWatchButton.tsx b/src/StopWatchButton.tsx index 7f12a5f1..72887cbe 100644 --- a/src/StopWatchButton.tsx +++ b/src/StopWatchButton.tsx @@ -1,54 +1,17 @@ -import React from 'react' +import React from 'react'; +import { IStopWatchButtonProps } from './type/InterfaceSWBP'; +import './css/App.css'; -// Maximum number of laps that can be recorded -const maxLaps = 25; - -// Define the props for the StopWatchButton component -type StopWatchButtonProps = { - type: 'start' | 'stop' | 'lap' | 'reset'; - onClick?: () => void; - timerOn?: boolean; - time?: number; - lapTimes?: number[]; -}; - - export default function StopWatchButton({ type, onClick, timerOn, time, lapTimes }: StopWatchButtonProps) { - // Determine the button text based on the type and add corresponding tabIndex - let buttonText, tabIndex; - switch(type) { - case 'start': - buttonText = 'Start'; - tabIndex = 1; - break; - case 'stop': - buttonText = 'Stop'; - tabIndex = 2; - break; - case 'lap': - buttonText = 'Record Lap'; - tabIndex = 3; - break; - case 'reset': - buttonText = 'Reset'; - tabIndex = 4; - break; - default: - buttonText = ''; - tabIndex = 0; +export default function StopWatchButton(props: IStopWatchButtonProps) { + const handleButtonClick = () => { + if (props.onClick) { + props.onClick; } - // Determine whether the reset or lap buttons should be disabled - const isLapDisabled = !timerOn || (lapTimes && lapTimes.length === 25); - const isResetDisabled = time === 0; - return( - - ) -} \ No newline at end of file + }; + + return ( +
+ +
+ ); +} diff --git a/src/type/InterfaceSWBP.tsx b/src/type/InterfaceSWBP.tsx new file mode 100644 index 00000000..4ce4fa80 --- /dev/null +++ b/src/type/InterfaceSWBP.tsx @@ -0,0 +1,4 @@ +export interface IStopWatchButtonProps { + type: string, + onClick: (e:any) => void +} \ No newline at end of file diff --git a/src/type/InterfaceSWT b/src/type/InterfaceSWT new file mode 100644 index 00000000..1ca78790 --- /dev/null +++ b/src/type/InterfaceSWT @@ -0,0 +1,4 @@ +export interface IStopWatchProps { + isRunning: boolean, + reset: boolean +} From 6d29d1dad4664b325afe159896c20bfb9acce3c3 Mon Sep 17 00:00:00 2001 From: davidy838 Date: Mon, 29 Jan 2024 22:11:58 -0800 Subject: [PATCH 3/3] final --- src/App.tsx | 117 ++++++++++++++++++++++++++++++------- src/StopWatch.tsx | 61 +++++-------------- src/StopWatchButton.tsx | 26 ++++----- src/type/InterfaceSWBP.tsx | 4 +- src/type/InterfaceSWT | 4 -- src/type/InterfaceSWT.tsx | 3 + 6 files changed, 129 insertions(+), 86 deletions(-) delete mode 100644 src/type/InterfaceSWT create mode 100644 src/type/InterfaceSWT.tsx diff --git a/src/App.tsx b/src/App.tsx index 971810a3..0d868082 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,28 +1,105 @@ -import React, { useState } from 'react' -import StopWatch from './StopWatch' -import './css/App.css' -import StopWatchButton from './StopWatchButton' +import React, { useEffect, useState } from 'react'; +import StopWatch from './StopWatch'; +import './css/App.css'; +import StopWatchButton from './StopWatchButton'; - export default function App() { - const [isRunning, setIsRunning] = useState(false); - const [reset, setReset] = useState(false); +export default function App() { + // State variables for managing stopwatch functionality + const [isRunning, setIsRunning] = useState(false); + const [reset, setReset] = useState(false); + const [time, setTime] = useState(0); + const [timeDisplay, setTimeDisplay] = useState>([]); + const [laps, setLaps] = useState>([]); - const handleOnStart = () => { - setIsRunning(true); - } + // Converts time in seconds to a displayable format (HH:MM:SS) + const convertTimeToDisplay = (time: number): (number | string)[] => { + const hours = Math.floor(time / 3600); + const minutes = Math.floor((time - hours * 3600) / 60); + const seconds = time - minutes * 60 - hours * 3600; + return [ + hours < 10 ? `0${hours}` : hours, + minutes < 10 ? `0${minutes}` : minutes, + seconds < 10 ? `0${seconds}` : seconds + ]; + } + + // Handles the start button click, sets isRunning to true and initializes laps if the timer has not started + const handleOnStart = () => { + setIsRunning(true); + if (time === 0) { + initializeLaps(); + } + } + + // Handles the stop button click, sets isRunning to false to pause the timer + const handleOnStop = () => { + setIsRunning(false); + } + + // Handles the reset button click, sets reset to true triggering a reset in useEffect + const handleReset = () => { + setReset(true); + } - const handleOnStop = () => { - setIsRunning(false); - } + // Initializes laps with time set to 0 + const initializeLaps = () => { + setLaps([0]); + } - const handleReset = () => { - setReset(true); + // Handles the lap button click, adds the current time to the laps array + const handleLap = () => { + if (time !== 0) { + setLaps((prevLaps) => [...prevLaps, time]); + } } - return( + + // useEffect to manage timer logic and updates + useEffect(() => { + let intervalId: NodeJS.Timeout; + + // Increment time every 1000ms if isRunning is true + if (isRunning) { + intervalId = setInterval(() => setTime((prevTime) => prevTime + 1), 1000); + } else { + clearInterval(intervalId); // Stop the timer if isRunning is false + } + + // Reset the timer, laps, and display when reset is true + if (reset) { + setIsRunning(false); + setTime(0); + setReset(false); + setLaps([]); + } + + // Update time display + setTimeDisplay(convertTimeToDisplay(time)); + + // Cleanup: clear the interval to avoid memory leaks + return () => { + clearInterval(intervalId); + }; + }, [reset, isRunning, time]); + + // App display with Stopwatch, buttons, and lap list + return (
- - - + +
+ + + + +
+
+
    + {laps.map((lap, index, array) => ( +
  • + {index > 0 ? `Lap ${index}: ${lap - array[index - 1]} seconds` : ''} +
  • + ))} +
+
) -} +} diff --git a/src/StopWatch.tsx b/src/StopWatch.tsx index 3897e086..11d9773d 100644 --- a/src/StopWatch.tsx +++ b/src/StopWatch.tsx @@ -1,48 +1,15 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; import './css/App.css'; - -export default function StopWatch(props: any) { - const [time, setTime] = useState(0); - const [timeDisplay, setTimeDisplay] = useState>([]); - const [intervalNumber, setIntervalNumber] = useState(0); - - const convertTimeToDisplay = (time: number): (number | string)[] => { - const formatTime = (value: number): string => (value < 10 ? `0${value}` : `${value}`); - - const hours = Math.floor(time / 3600); - const minutes = Math.floor((time - hours * 3600) / 60); - const seconds = time - minutes * 60 - hours * 3600; - - return [formatTime(hours), formatTime(minutes), formatTime(seconds)]; - }; - - useEffect(() => { - const handleInterval = () => { - setTime((prevTime) => prevTime + 1); - }; - - if (props.isRunning) { - const interval = setInterval(handleInterval, 1000); - setIntervalNumber(interval); - } else { - clearInterval(intervalNumber); - } - - if (props.reset) { - clearInterval(intervalNumber); - setTime(0); - } - - setTimeDisplay(convertTimeToDisplay(time)); - }, [props.isRunning, props.reset, time, intervalNumber]); - - return ( -
-

{timeDisplay[0]}

- : -

{timeDisplay[1]}

- : -

{timeDisplay[2]}

-
- ); -} +import { IStopWatchProps } from './type/InterfaceSWT'; + +export default function StopWatch(props: IStopWatchProps) { + return ( +
+

{props.timeDisplay[0]}

+ : +

{props.timeDisplay[1]}

+ : +

{props.timeDisplay[2]}

+
+ ) +} \ No newline at end of file diff --git a/src/StopWatchButton.tsx b/src/StopWatchButton.tsx index 72887cbe..98abebe2 100644 --- a/src/StopWatchButton.tsx +++ b/src/StopWatchButton.tsx @@ -1,17 +1,17 @@ import React from 'react'; -import { IStopWatchButtonProps } from './type/InterfaceSWBP'; import './css/App.css'; +import { IStopWatchProps } from './type/InterfaceSWT'; -export default function StopWatchButton(props: IStopWatchButtonProps) { - const handleButtonClick = () => { - if (props.onClick) { - props.onClick; - } - }; +export default function StopWatch(props: IStopWatchProps) { - return ( -
- -
- ); -} + // Stopwatch Display with hours, minutes, seconds + return ( +
+

{props.timeDisplay[0]}

+ : +

{props.timeDisplay[1]}

+ : +

{props.timeDisplay[2]}

+
+ ) +} \ No newline at end of file diff --git a/src/type/InterfaceSWBP.tsx b/src/type/InterfaceSWBP.tsx index 4ce4fa80..82dd601d 100644 --- a/src/type/InterfaceSWBP.tsx +++ b/src/type/InterfaceSWBP.tsx @@ -1,4 +1,4 @@ export interface IStopWatchButtonProps { type: string, - onClick: (e:any) => void -} \ No newline at end of file + onClick: (e:any) => void, +} \ No newline at end of file diff --git a/src/type/InterfaceSWT b/src/type/InterfaceSWT deleted file mode 100644 index 1ca78790..00000000 --- a/src/type/InterfaceSWT +++ /dev/null @@ -1,4 +0,0 @@ -export interface IStopWatchProps { - isRunning: boolean, - reset: boolean -} diff --git a/src/type/InterfaceSWT.tsx b/src/type/InterfaceSWT.tsx new file mode 100644 index 00000000..0cad3aaf --- /dev/null +++ b/src/type/InterfaceSWT.tsx @@ -0,0 +1,3 @@ +export interface IStopWatchProps { + timeDisplay: (string | number)[] +}