This commit is contained in:
Francesco Esposito 2019-03-15 15:24:47 +01:00
parent 88f4919a22
commit 4d76a04872
6 changed files with 141 additions and 50 deletions

5
package-lock.json generated
View File

@ -13014,6 +13014,11 @@
"react-lifecycles-compat": "^3.0.4"
}
},
"react-web-notification": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/react-web-notification/-/react-web-notification-0.5.0.tgz",
"integrity": "sha512-xdUHyOMBP+RlhAwuvYd0WlNvT11tW7V4/vXnp3ApbmChyT6FE/4KYEcmeXw+RtP1cJH8B1KbT7RiYNHFnY/bcQ=="
},
"reactstrap": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-7.1.0.tgz",

View File

@ -2,11 +2,16 @@
"name": "tomadoro",
"version": "0.1.0",
"private": true,
"engines": {
"npm": "6.4.1",
"node": "10.15.0"
},
"dependencies": {
"bootstrap": "^4.3.1",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-scripts": "2.1.8",
"react-web-notification": "^0.5.0",
"reactstrap": "^7.1.0"
},
"scripts": {

View File

@ -1,5 +1,5 @@
body {
background-color: #d9534f;
background-color: #db4f4a;
}
.title {
@ -13,26 +13,21 @@ body {
}
.App-header {
background-color: #d9534f;
background-color: #c34944;
display: flex;
min-height: 20vh;
min-height: 10vh;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(50px + 2vmin);
font-size: calc(40px + 2vmin);
color: white;
text-shadow: 0 0 2px #cf4242, 0 0 4px #2B2D1F;
margin-bottom: 25px;
box-shadow: 0 0 2px #cf4242, 0 0 8px #2B2D1F;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -3,35 +3,33 @@
font-weight: 600;
text-shadow: 0 0 2px #cf4242, 0 0 4px #2B2D1F;
color: white;
opacity: 1;
}
.buttons {
font-size: 22px;
font-weight: bold;
height: 70px;
border-radius: 30px;
box-shadow: 0 0 1px #03030349, 0 0 10px rgb(155, 156, 150);
opacity: 0.9;
font-size: 20px;
font-weight: normal;
height: 60px;
border-radius: 25px;
box-shadow: 0 0 5px #161616, 0 0 2px #2B2D1F;
opacity: 0.9;
}
.top-buffer {
margin-top: 15px;
.top-margin {
margin-top: 18px;
}
.buttons-box {
background-color: rgba(255, 255, 255, 0.9); /* transparent */
background-color: rgba(255, 255, 255, 1.0);
padding: 20px;
margin-top: 5px;
border-radius: 30px;
opacity: 1;
box-shadow: 0 0 2px #cf4242, 0 0 5px rgb(243, 243, 243);
border-radius: 25px;
box-shadow: 0 0 1px #cf4242, 0 0 10px #2B2D1F;
}
.App-logo {
height: 25vmin;
pointer-events: none;
filter: drop-shadow(0 0 1.00rem rgb(211, 204, 206));
filter: drop-shadow(0 0 0.30rem rgb(250, 223, 174));
align-items: center;
justify-content: center;
text-align: center;
@ -50,14 +48,4 @@
to {
transform: rotate(360deg);
}
}
.App-logo-scale {
animation: crescendo 0.5s alternate infinite ease-in;
}
@keyframes crescendo {
0% {transform: scale(.8);}
100% {transform: scale(1.2);}
}

View File

@ -2,29 +2,103 @@ import React, { Component } from 'react';
import { Button, Container, Row, Col } from 'reactstrap';
import './Timer.css';
import logo from '../logo.svg';
import Notification from 'react-web-notification';
import sound from '../sound.mp3';
class Timer extends Component {
constructor(props) {
super(props);
this.defaultSeconds = 1500;
this.defaultSeconds = 4;
this.defaultLogoSpin = 'App-logo-rotation';
this.state = {
seconds: this.defaultSeconds,
started: false,
logoSpin: ''
logoSpin: '',
ignore: true,
title: ''
};
// binding
this.startTimer = this.startTimer.bind(this);
this.stopTimer = this.stopTimer.bind(this);
this.resetTimer = this.resetTimer.bind(this);
this.handlePermissionGranted = this.handlePermissionGranted.bind(this);
this.handlePermissionDenied = this.handlePermissionDenied.bind(this);
this.handleNotSupported = this.handleNotSupported.bind(this);
this.sendNotification = this.sendNotification.bind(this);
this.handleNotificationOnShow = this.handleNotificationOnShow.bind(this);
this.playSound = this.playSound.bind(this);
}
fmtMSS(s){return(s-(s%=60))/60+(9<s?':':':0')+s}
formatMinute(s){
return ( s - ( s%=60 )) / 60 + ( 9 < s ?':':':0') +s;
}
// handleNotification
handlePermissionGranted() {
this.setState({
ignore: false
});
}
handlePermissionDenied() {
this.setState({
ignore: true
});
}
handleNotSupported() {
this.setState({
ignore: true
});
}
handleNotificationOnError(e, tag){
console.log(e, 'Notification error tag:' + tag);
}
playSound(){
document.getElementById('sound').play();
}
handleNotificationOnShow() {
this.playSound();
}
sendNotification() {
if (this.state.ignore) {
return;
}
const title = 'tomadoro';
const body = 'Time is Up! 🍅';
const tag = Date.now();
const options = {
tag: tag,
body: body,
lang: 'en',
sound: {sound}
};
this.setState({
title: title,
options: options
});
}
tick() {
this.setState(state => ({
seconds: state.seconds -1
}));
if (this.state.seconds <= 0) {
this.sendNotification();
this.stopTimer();
this.resetTimer();
}
}
startTimer() {
@ -54,22 +128,27 @@ class Timer extends Component {
<Container>
<Row>
<Col>
<img className={`App-logo ${this.state.logoSpin}`} src={logo}/>
<img
className={`App-logo ${this.state.logoSpin}`}
src={logo}
alt="Tomato"
>
</img>
</Col>
</Row>
<Row>
<Col>
<p
class="timer"
className="timer"
>
{this.fmtMSS(this.state.seconds)}
{this.formatMinute(this.state.seconds)}
</p>
</Col>
</Row>
<div className="buttons-box">
<Row>
<Col>
<Button
<Button
className="buttons"
block
size="lg"
@ -77,13 +156,15 @@ class Timer extends Component {
onClick={this.startTimer}
disabled={this.state.started}
>
Start
START
</Button>
</Col>
</Row>
<Row className="top-buffer">
<Row
className="top-margin"
>
<Col>
<Button
<Button
className="buttons"
block
color="danger"
@ -91,26 +172,43 @@ class Timer extends Component {
onClick={this.stopTimer}
disabled={!this.state.started}
>
Stop
STOP
</Button>
</Col>
<Col>
<Button
<Button
className="buttons"
block
color="secondary"
size="lg"
onClick={this.resetTimer}
disabled={this.state.started}
disabled={this.state.started || this.state.seconds === this.defaultSeconds}
>
Reset
RESET
</Button>
</Col>
</Row>
</div>
</Container>
<Notification
ignore={this.state.ignore}
onPermissionGranted = {this.handlePermissionGranted}
onPermissionDenied = {this.handlePermissionDenied}
notSupported = {this.handleNotSupported}
onError = {this.onError}
timeout = {5000}
title = {this.state.title}
options = {this.state.options}
onShow = {this.handleNotificationOnShow}
>
</Notification>
<audio id='sound' preload='auto'>
<source src={sound} type='audio/mpeg' />
<embed hidden src={sound} />
</audio>
</div>
);
}

BIN
src/sound.mp3 Normal file

Binary file not shown.