add pomodoro-clock-react

This commit is contained in:
2020-06-11 01:36:48 -07:00
parent 01bbb2d626
commit f3b0a0721a
59 changed files with 15628 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { connect } from "react-redux";
import PomodoroClock from './PomodoroClock';
import { colors } from './globals'
import { innerWindowWidthAction } from "./actions/innerWindowWidthAction";
import { innerWindowHeightAction } from "./actions/innerWindowHeightAction";
const mapStateToProps = (state) => ({ ...state });
const mapDispatchToProps = (dispatch) => ({
innerWindowWidthAction: (windowInnerWidth) => dispatch(innerWindowWidthAction(windowInnerWidth)),
innerWindowHeightAction: (windowInnerHeight) => dispatch(innerWindowHeightAction(windowInnerHeight)),
});
class App extends React.Component {
constructor(props) {
super(props);
this.handleResize = this.handleResize.bind(this);
};
handleResize() {
this.props.innerWindowWidthAction(window.innerWidth);
this.props.innerWindowHeightAction(window.innerHeight);
};
componentDidMount() {
window.addEventListener('resize',this.handleResize);
};
componentWillUnmount() {
window.removeEventListener('resize',this.handleResize);
};
render() {
const height = this.props.innerWindowHeight;
const width = this.props.innerWindowWidth;
const gitHubLabelStyle = {
backgroundColor: colors.prussianBlue,
border: 'none',
width: 149,
height: 0,
position: 'absolute',
right: 0,
};
const attachmentStyle = {
position: 'absolute',
right: 0,
height: 149,
width: 149,
};
const style = {
textAlign: 'center',
backgroundColor: colors.prussianBlue,
height: '100vh',
width: '100vw',
display: 'flex',
flexDirection: 'column',
color: colors.ivoryBlack,
overFlow: 'hidden',
fontFamily: 'Ubuntu sans-serif',
fontSize: 48,
};
if (width > height && height < 400) {
gitHubLabelStyle['width'] = 99;
attachmentStyle['width'] = 99;
attachmentStyle['height'] = 99;
} else if (height > width) {
gitHubLabelStyle['width'] = 99;
attachmentStyle['width'] = 99;
attachmentStyle['height'] = 99;
}
return (
<div style={style}>
<a href="https://github.com/TrentSPalmer/fcc-challenges/tree/gh-pages/pomodoro-clock-react"
style={gitHubLabelStyle}
target="_blank" rel="noopener noreferrer">
<img
src="https://github.blog/wp-content/uploads/2008/12/forkme_right_white_ffffff.png?resize=149%2C149"
className="size-full"
style={attachmentStyle}
alt="Fork me on GitHub"
data-recalc-dims="1">
</img>
</a>
<PomodoroClock />
</div>
);
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);

View File

@@ -0,0 +1,9 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

Binary file not shown.

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { colors } from './globals'
import BreakControls from './BreakControls';
const Break = () => {
const style = {
height: '100%',
width: '50%',
};
const breakTextStyle = {
height: '50%',
width: '90%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'right',
marginRight: '10%',
color: colors.goldLeaf,
};
return(
<div style={style}>
<div id="break-label" style={breakTextStyle}>Break Length</div>
<BreakControls />
</div>
);
};
export default Break;

View File

@@ -0,0 +1,124 @@
import React from 'react';
import { connect } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
import { faArrowCircleUp } from "@fortawesome/free-solid-svg-icons";
import { colors,makeClock } from './globals'
import { breakLengthAction } from "./actions/breakLengthAction";
import { clockAction } from "./actions/clockAction";
const mapDispatchToProps = (dispatch) => ({
breakLengthAction: (breakLength) => dispatch(breakLengthAction(breakLength)),
clockAction: (time) => dispatch(clockAction(time)),
});
const mapStateToProps = (state) => ({ ...state });
class BreakControls extends React.Component {
constructor(props) {
super(props);
this.handleDownArrowMouseEvent = this.handleDownArrowMouseEvent.bind(this);
this.handleUpArrowMouseEvent = this.handleUpArrowMouseEvent.bind(this);
this.incrementBreak = this.incrementBreak.bind(this);
this.decrementBreak = this.decrementBreak.bind(this);
this.state = {
downColor: colors.ivoryBlack,
upColor: colors.ivoryBlack,
};
};
incrementBreak() {
if (this.props.breakLength < 60) {
const newBreakLength = this.props.breakLength + 1;
if (!this.props.clockIsRunning) {
this.props.breakLengthAction(newBreakLength);
if (this.props.timer === 'Break') {
this.props.clockAction(makeClock(newBreakLength * 60));
}
} else {
if (this.props.timer === 'Session') {
this.props.breakLengthAction(newBreakLength);
}
}
}
};
decrementBreak() {
if (this.props.breakLength > 1) {
const newBreakLength = this.props.breakLength - 1;
if (!this.props.clockIsRunning) {
this.props.breakLengthAction(newBreakLength);
if (this.props.timer === 'Break') {
this.props.clockAction(makeClock(newBreakLength * 60));
}
} else {
if (this.props.timer === 'Session') {
this.props.breakLengthAction(newBreakLength);
}
}
}
};
handleDownArrowMouseEvent(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({downColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({downColor: colors.ivoryBlack});
}
};
handleUpArrowMouseEvent(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({upColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({upColor: colors.ivoryBlack});
}
};
render() {
const style = {
height: '50%',
width: '75%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
marginRight: '25%',
fontSize: 32,
};
const breakStyle = {
width: 50,
}
const downArrowStyle = {
color: this.state.downColor,
}
const upArrowStyle = {
color: this.state.upColor,
}
return(
<div style={style}>
<div id='break-decrement' style={downArrowStyle} onClick={this.decrementBreak}
onTouchStart={this.handleDownArrowMouseEvent} onTouchEnd={this.handleDownArrowMouseEvent}
onMouseDown={this.handleDownArrowMouseEvent} onMouseUp={this.handleDownArrowMouseEvent}>
<FontAwesomeIcon icon={faArrowCircleDown}/>
</div>
<p id='break-length' style={breakStyle}>{this.props.breakLength}</p>
<div id='break-increment' style={upArrowStyle} onClick={this.incrementBreak}
onTouchStart={this.handleUpArrowMouseEvent} onTouchEnd={this.handleUpArrowMouseEvent}
onMouseDown={this.handleUpArrowMouseEvent} onMouseUp={this.handleUpArrowMouseEvent}>
<FontAwesomeIcon icon={faArrowCircleUp}/>
</div>
</div>
);
};
};
export default connect(mapStateToProps, mapDispatchToProps)(BreakControls);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import { connect } from "react-redux";
import { colors } from './globals'
const mapStateToProps = (state) => ({ ...state });
class Clock extends React.Component {
render() {
const clockStyle = {
height: '40%',
width: '100%',
}
const timerLabelStyle = {
height: '30%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
color: colors.goldLeaf,
}
const timerStyle = {
height: '70%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
fontSize: 96,
}
return(
<div style={clockStyle}>
<div id='timer-label' style={timerLabelStyle}>{this.props.timer}</div>
<div id='time-left' style={timerStyle}>{this.props.clock}</div>
</div>
);
};
};
export default connect(mapStateToProps)(Clock);

View File

@@ -0,0 +1,186 @@
import React from 'react';
import { connect } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlayCircle } from "@fortawesome/free-solid-svg-icons";
import { faPauseCircle } from "@fortawesome/free-solid-svg-icons";
import { faSyncAlt } from "@fortawesome/free-solid-svg-icons";
import { colors,makeClock } from './globals'
import { clockIsRunningAction } from "./actions/clockIsRunningAction";
import { zeroTimeAction } from "./actions/zeroTimeAction";
import { clockAction } from "./actions/clockAction";
import { timerAction } from "./actions/timerAction";
import { sessionLengthAction } from "./actions/sessionLengthAction";
import { breakLengthAction } from "./actions/breakLengthAction";
import BeepSoundOgg from './BeepSound.ogg';
const mapDispatchToProps = (dispatch) => ({
clockIsRunningAction: (clockIsRunning) => dispatch(clockIsRunningAction(clockIsRunning)),
zeroTimeAction: (zeroTime) => dispatch(zeroTimeAction(zeroTime)),
clockAction: (time) => dispatch(clockAction(time)),
timerAction: (timer) => dispatch(timerAction(timer)),
sessionLengthAction: (sessionLength) => dispatch(sessionLengthAction(sessionLength)),
breakLengthAction: (breakLength) => dispatch(breakLengthAction(breakLength)),
});
const mapStateToProps = (state) => ({ ...state });
class Controls extends React.Component {
constructor(props) {
super(props);
this.handleMouseEventPlay = this.handleMouseEventPlay.bind(this);
this.handleMouseEventPause = this.handleMouseEventPause.bind(this);
this.handleMouseEventReset = this.handleMouseEventReset.bind(this);
this.handlePlay = this.handlePlay.bind(this);
this.handleReset = this.handleReset.bind(this);
this.state = {
playColor: colors.ivoryBlack,
pauseColor: colors.ivoryBlack,
resetColor: colors.ivoryBlack,
sound: '',
};
};
handlePlay() {
if (this.props.clockIsRunning) {
this.props.clockIsRunningAction(false);
} else {
this.props.clockIsRunningAction(true);
const now = new Date();
const nowSeconds = Math.floor(now.getTime() / 1000);
const remainingTime = this.props.clock.split(':');
const remainingSeconds = (60 * parseInt(remainingTime[0])) + parseInt(remainingTime[1]);
this.props.zeroTimeAction(nowSeconds + remainingSeconds);
const self = this;
let refreshClock = setInterval(function(){
if (!self.props.clockIsRunning) {
clearInterval(refreshClock);
} else {
const now = new Date();
const nowSeconds = Math.floor(now.getTime() / 1000);
if (nowSeconds <= self.props.zeroTime) {
const remainingTime = self.props.zeroTime - nowSeconds;
const newClock = makeClock(remainingTime);
if (newClock !== self.props.clock) {
self.props.clockAction(newClock);
}
} else {
let remainingTime = self.props.breakLength * 60;
if (self.props.timer === 'Session') {
self.props.timerAction('Break');
} else {
self.props.timerAction('Session');
remainingTime = self.props.sessionLength * 60;
}
self.props.zeroTimeAction(nowSeconds + remainingTime);
self.props.clockAction(makeClock(remainingTime));
}
}
},1000);
}
};
handleReset() {
if (this.props.clockIsRunning) {
this.props.clockIsRunningAction(false);
}
if (this.props.timer === 'Break') {
this.props.timerAction('Session');
}
if (this.props.sessionLength !== 25) {
this.props.sessionLengthAction(25);
}
if (this.props.breakLength !== 5) {
this.props.breakLengthAction(5);
}
if (this.props.clock !== '25:00') {
this.props.clockAction('25:00');
}
this.state.sound.pause();
const oldSound = this.state.sound;
oldSound.currentTime = 0;
};
componentDidUpdate(prevProps) {
if (this.props.clock === '00:00') {
this.state.sound.play();
}
};
handleMouseEventPlay(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({playColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({playColor: colors.ivoryBlack});
}
};
handleMouseEventPause(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({pauseColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({pauseColor: colors.ivoryBlack});
}
};
handleMouseEventReset(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({resetColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({resetColor: colors.ivoryBlack});
}
};
componentDidMount() {
this.setState({sound: document.getElementById('beep')});
};
render() {
const controlsStyle = {
height: '20%',
width: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
fontSize: 48,
}
const playStyle = {
color: this.state.playColor,
};
const pauseStyle = {
color: this.state.pauseColor,
width: 150,
};
const resetStyle = {
color: this.state.resetColor,
};
return(
<div style={controlsStyle}>
<div id='start_stop' style={playStyle} onClick={this.handlePlay}
onMouseDown={this.handleMouseEventPlay} onMouseUp={this.handleMouseEventPlay}
onTouchStart={this.handleMouseEventPlay} onTouchEnd={this.handleMouseEventPlay}>
<FontAwesomeIcon icon={faPlayCircle}/>
</div>
<FontAwesomeIcon style={pauseStyle} onMouseDown={this.handleMouseEventPause}
onMouseUp={this.handleMouseEventPause} onTouchStart={this.handleMouseEventPause}
onTouchEnd={this.handleMouseEventPause} onClick={() => {this.props.clockIsRunningAction(false)}} icon={faPauseCircle}/>
<div id='reset' style={resetStyle} onClick={this.handleReset}
onMouseDown={this.handleMouseEventReset} onMouseUp={this.handleMouseEventReset}
onTouchStart={this.handleMouseEventReset} onTouchEnd={this.handleMouseEventReset}>
<FontAwesomeIcon icon={faSyncAlt}/>
</div>
<audio id='beep' preload='auto' src={BeepSoundOgg} type='audio/ogg'></audio>
</div>
);
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Controls);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { connect } from "react-redux";
import { colors } from './globals'
import Top from './Top';
import Clock from './Clock';
import Controls from './Controls';
const mapStateToProps = (state) => ({ ...state });
class PomodoroClock extends React.Component {
render() {
const width = this.props.innerWindowWidth;
const height = this.props.innerWindowHeight;
const eightyPercentWidth = Math.round(width * 0.8);
const eightyPercentHeight = Math.round(height * 0.8);
const maxHeight = 500;
const maxWidth = 800;
const style = {
margin: 'auto',
border: '3px solid',
borderColor: colors.ivoryBlack,
zIndex: '1',
backgroundColor: colors.skyBlue,
borderRadius: 10,
height: eightyPercentHeight < maxHeight ? eightyPercentHeight : maxHeight,
width: eightyPercentWidth < maxWidth ? eightyPercentWidth : maxWidth,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
fontSize: 32,
userSelect: 'none',
MozUserSelect: 'none',
WebkitUserSelect: 'none',
};
const clockContainerStyle = {
height: '100%',
width: '100%',
};
if (width > height && height < 400) {
style['height'] = '96vh';
style['width'] = '75vw';
style['margin'] = '1vh auto 3vh auto';
style['fontSize'] = 24;
} else if (height > width) {
style['height'] = Math.round(height * 0.75);
style['maxHeight'] = maxHeight;
clockContainerStyle['height'] = width > style['height'] ? style['height'] : width;
clockContainerStyle['maxHeight'] = maxHeight;
style['width'] = width - 6;
style['maxWidth'] = maxWidth;
style['fontSize'] = 24;
}
return(
<div style={style}>
<div style={clockContainerStyle}>
<Top />
<Clock />
<Controls />
</div>
</div>
);
};
};
export default connect(mapStateToProps)(PomodoroClock);

View File

@@ -0,0 +1,31 @@
import React from 'react';
import { colors } from './globals'
import SessionControls from './SessionControls';
const Session = () => {
const style = {
height: '100%',
width: '50%',
};
const sessionTextStyle = {
height: '50%',
width: '90%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
textAlign: 'left',
marginLeft: '10%',
color: colors.goldLeaf,
};
return(
<div style={style}>
<div id="session-label" style={sessionTextStyle}>Session Length</div>
<SessionControls />
</div>
);
};
export default Session;

View File

@@ -0,0 +1,124 @@
import React from 'react';
import { connect } from "react-redux";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
import { faArrowCircleUp } from "@fortawesome/free-solid-svg-icons";
import { colors,makeClock } from './globals'
import { sessionLengthAction } from "./actions/sessionLengthAction";
import { clockAction } from "./actions/clockAction";
const mapDispatchToProps = (dispatch) => ({
sessionLengthAction: (sessionLength) => dispatch(sessionLengthAction(sessionLength)),
clockAction: (time) => dispatch(clockAction(time)),
});
const mapStateToProps = (state) => ({ ...state });
class SessionControls extends React.Component {
constructor(props) {
super(props);
this.handleDownArrowMouseEvent = this.handleDownArrowMouseEvent.bind(this);
this.handleUpArrowMouseEvent = this.handleUpArrowMouseEvent.bind(this);
this.incrementSession = this.incrementSession.bind(this);
this.decrementSession = this.decrementSession.bind(this);
this.state = {
downColor: colors.ivoryBlack,
upColor: colors.ivoryBlack,
};
};
incrementSession() {
if (this.props.sessionLength < 60) {
const newSessionLength = this.props.sessionLength + 1;
if (!this.props.clockIsRunning) {
this.props.sessionLengthAction(newSessionLength);
if (this.props.timer === 'Session') {
this.props.clockAction(makeClock(newSessionLength * 60));
}
} else {
if (this.props.timer === 'Break') {
this.props.sessionLengthAction(newSessionLength);
}
}
}
};
decrementSession() {
if (this.props.sessionLength > 1) {
const newSessionLength = this.props.sessionLength - 1;
if (!this.props.clockIsRunning) {
this.props.sessionLengthAction(newSessionLength);
if (this.props.timer === 'Session') {
this.props.clockAction(makeClock(newSessionLength * 60));
}
} else {
if (this.props.timer === 'Break') {
this.props.sessionLengthAction(newSessionLength);
}
}
}
};
handleDownArrowMouseEvent(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({downColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({downColor: colors.ivoryBlack});
}
};
handleUpArrowMouseEvent(e) {
if (e.type === 'mousedown' || e.type === 'touchstart') {
this.setState({upColor: colors.goldLeaf});
} else if (e.type === 'mouseup' || e.type === 'touchend') {
this.setState({upColor: colors.ivoryBlack});
}
};
render() {
const style = {
height: '50%',
width: '75%',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
marginLeft: '25%',
fontSize: 32,
};
const breakStyle = {
width: 50,
}
const downArrowStyle = {
color: this.state.downColor,
}
const upArrowStyle = {
color: this.state.upColor,
}
return(
<div style={style}>
<div id='session-decrement' style={downArrowStyle} onClick={this.decrementSession}
onTouchStart={this.handleDownArrowMouseEvent} onTouchEnd={this.handleDownArrowMouseEvent}
onMouseDown={this.handleDownArrowMouseEvent} onMouseUp={this.handleDownArrowMouseEvent}>
<FontAwesomeIcon icon={faArrowCircleDown}/>
</div>
<p id='session-length' style={breakStyle}>{this.props.sessionLength}</p>
<div id='session-increment' style={upArrowStyle} onClick={this.incrementSession}
onTouchStart={this.handleUpArrowMouseEvent} onTouchEnd={this.handleUpArrowMouseEvent}
onMouseDown={this.handleUpArrowMouseEvent} onMouseUp={this.handleUpArrowMouseEvent}>
<FontAwesomeIcon icon={faArrowCircleUp}/>
</div>
</div>
);
};
};
export default connect(mapStateToProps, mapDispatchToProps)(SessionControls);

View File

@@ -0,0 +1,39 @@
import React from 'react';
import Break from './Break';
import Session from './Session';
const Top = () => {
const style = {
height: '40%',
width: '100%',
}
const titleStyle = {
height: '50%',
width: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
fontSize: 48,
}
const breakAndSessionStyle = {
height: '50%',
width: '100%',
display: 'flex',
}
return(
<div style={style}>
<div style={titleStyle}>Pomodoro Clock</div>
<div style={breakAndSessionStyle}>
<Break />
<Session />
</div>
</div>
);
}
export default Top;

View File

@@ -0,0 +1,8 @@
export const SETBREAKLENGTH = "SETBREAKLENGTH";
export const breakLengthAction = (breakLength) => {
return {
type: SETBREAKLENGTH,
breakLength: breakLength,
};
};

View File

@@ -0,0 +1,8 @@
export const SETCLOCK = "SETCLOCK";
export const clockAction = (time) => {
return {
type: SETCLOCK,
time: time,
};
};

View File

@@ -0,0 +1,8 @@
export const SETCLOCKISRUNNING = "SETCLOCKISRUNNING";
export const clockIsRunningAction = (clockIsRunning) => {
return {
type: SETCLOCKISRUNNING,
clockIsRunning: clockIsRunning,
};
};

View File

@@ -0,0 +1,8 @@
export const SETWINDOWINNERHEIGHT = "SETWINDOWINNERHEIGHT";
export const innerWindowHeightAction = (windowInnerHeight) => {
return {
type: SETWINDOWINNERHEIGHT,
windowInnerHeight: windowInnerHeight,
};
};

View File

@@ -0,0 +1,8 @@
export const SETWINDOWINNERWIDTH = "SETWINDOWINNERWIDTH";
export const innerWindowWidthAction = (windowInnerWidth) => {
return {
type: SETWINDOWINNERWIDTH,
windowInnerWidth: windowInnerWidth,
};
};

View File

@@ -0,0 +1,8 @@
export const SETSESSIONLENGTH = "SETSESSIONLENGTH";
export const sessionLengthAction = (sessionLength) => {
return {
type: SETSESSIONLENGTH,
sessionLength: sessionLength,
};
};

View File

@@ -0,0 +1,8 @@
export const SETTIMER = "SETTIMER";
export const timerAction = (timer) => {
return {
type: SETTIMER,
timer: timer,
};
};

View File

@@ -0,0 +1,8 @@
export const SETZEROTIME = "SETZEROTIME";
export const zeroTimeAction = (zeroTime) => {
return {
type: SETZEROTIME,
zeroTime: zeroTime,
};
};

View File

@@ -0,0 +1,14 @@
export const colors = {
prussianBlue: '#0b3c5d',
skyBlue: '#328cc1',
goldLeaf: '#d9b310',
ivoryBlack: '#1d2731',
};
export const makeClock = (remainingTime) => {
const remainingSeconds = remainingTime % 60;
const remainingMinutes = Math.floor(remainingTime / 60);
const minutes = remainingMinutes > 9 ? remainingMinutes.toString() : '0' + remainingMinutes.toString() ;
const seconds = remainingSeconds > 9 ? remainingSeconds.toString() : '0' + remainingSeconds.toString() ;
return minutes + ':' + seconds;
};

View File

@@ -0,0 +1,14 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "react-redux";
import store from "./store";
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@@ -0,0 +1,14 @@
import { SETBREAKLENGTH } from "../actions/breakLengthAction";
export default (state, action) => {
if (!state) {
state = 5;
}
switch (action.type) {
case SETBREAKLENGTH:
return action.breakLength;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETCLOCKISRUNNING } from "../actions/clockIsRunningAction";
export default (state, action) => {
if (!state) {
state = false;
}
switch (action.type) {
case SETCLOCKISRUNNING:
return action.clockIsRunning;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETCLOCK } from "../actions/clockAction";
export default (state, action) => {
if (!state) {
state = '25:00';
}
switch (action.type) {
case SETCLOCK:
return action.time;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETWINDOWINNERHEIGHT } from "../actions/innerWindowHeightAction";
export default (state, action) => {
if (!state) {
state = window.innerHeight;
}
switch (action.type) {
case SETWINDOWINNERHEIGHT:
return action.windowInnerHeight;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETWINDOWINNERWIDTH } from "../actions/innerWindowWidthAction";
export default (state, action) => {
if (!state) {
state = window.innerWidth;
}
switch (action.type) {
case SETWINDOWINNERWIDTH:
return action.windowInnerWidth;
default:
return state;
};
};

View File

@@ -0,0 +1,20 @@
import { combineReducers } from "redux";
import innerWindowWidthReducer from "./innerWindowWidthReducer";
import innerWindowHeightReducer from "./innerWindowHeightReducer";
import breakLengthReducer from "./breakLengthReducer";
import sessionLengthReducer from "./sessionLengthReducer";
import timerReducer from "./timerReducer";
import clockReducer from "./clockReducer";
import clockIsRunningReducer from "./clockIsRunningReducer";
import zeroTimeReducer from "./zeroTimeReducer";
export default combineReducers({
innerWindowWidth: innerWindowWidthReducer,
innerWindowHeight: innerWindowHeightReducer,
breakLength: breakLengthReducer,
sessionLength: sessionLengthReducer,
timer: timerReducer,
clock: clockReducer,
clockIsRunning: clockIsRunningReducer,
zeroTime: zeroTimeReducer,
});

View File

@@ -0,0 +1,14 @@
import { SETSESSIONLENGTH } from "../actions/sessionLengthAction";
export default (state, action) => {
if (!state) {
state = 25;
}
switch (action.type) {
case SETSESSIONLENGTH:
return action.sessionLength;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETTIMER } from "../actions/timerAction";
export default (state, action) => {
if (!state) {
state = 'Session';
}
switch (action.type) {
case SETTIMER:
return action.timer;
default:
return state;
}
};

View File

@@ -0,0 +1,14 @@
import { SETZEROTIME } from "../actions/zeroTimeAction";
export default (state, action) => {
if (!state) {
state = 0;
}
switch (action.type) {
case SETZEROTIME:
return action.zeroTime;
default:
return state;
}
};

View File

@@ -0,0 +1,141 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

View File

@@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@@ -0,0 +1,6 @@
import { createStore } from "redux";
import rootReducer from "./reducers/rootReducer";
const store = createStore(rootReducer);
export default store;