...
 
Commits (2)
{
"extends": "react-app",
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
......@@ -3466,6 +3466,24 @@
"jsx-ast-utils": "1.4.1"
}
},
"eslint-plugin-prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.2.tgz",
"integrity": "sha512-tGek5clmW5swrAx1mdPYM8oThrBE83ePh7LeseZHBWfHVGrHPhKn7Y5zgRMbU/9D5Td9K4CEmUPjGxA7iw98Og==",
"dev": true,
"requires": {
"fast-diff": "1.1.2",
"jest-docblock": "21.2.0"
},
"dependencies": {
"jest-docblock": {
"version": "21.2.0",
"resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz",
"integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==",
"dev": true
}
}
},
"eslint-plugin-react": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.4.0.tgz",
......@@ -3858,6 +3876,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
},
"fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
......@@ -8961,6 +8985,12 @@
"resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
"integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks="
},
"prettier": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.14.2.tgz",
"integrity": "sha512-McHPg0n1pIke+A/4VcaS2en+pTNjy4xF+Uuq86u/5dyDO59/TtFZtQ708QIRkEZ3qwKz3GVkVa6mpxK/CpB8Rg==",
"dev": true
},
"pretty-bytes": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz",
......
......@@ -26,6 +26,8 @@
"babel-cli": "^6.26.0",
"babel-plugin-react-intl": "^2.4.0",
"babel-preset-react-app": "^3.1.2",
"eslint-plugin-prettier": "^2.6.2",
"prettier": "1.14.2",
"react-intl-translations-manager": "^5.0.3"
}
}
export const START_TALKING = 'START_TALKING';
export const STOP_TALKING = 'STOP_TALKING';
export const ALREADY_TALKED = 'ALREADY_TALKED';
export const TICK = 'TICK';
export const CHANGE_TIMER_LIMIT = 'CHANGE_TIMER_LIMIT';
export const START_TALKING = "START_TALKING";
export const STOP_TALKING = "STOP_TALKING";
export const ALREADY_TALKED = "ALREADY_TALKED";
export const TICK = "TICK";
export const CHANGE_TIMER_LIMIT = "CHANGE_TIMER_LIMIT";
function previousTalk(state) {
const current = state.current;
......@@ -12,8 +11,8 @@ function previousTalk(state) {
}
return {
category: current.category,
duration: Date.now() - current.startedAt,
}
duration: Date.now() - current.startedAt
};
}
export function startTalking(category) {
......@@ -22,9 +21,9 @@ export function startTalking(category) {
type: START_TALKING,
category,
timestamp: Date.now(),
previousTalk: previousTalk(getState()),
})
}
previousTalk: previousTalk(getState())
});
};
}
export function stopTalking() {
......@@ -32,9 +31,9 @@ export function stopTalking() {
const state = getState();
return dispatch({
type: STOP_TALKING,
previousTalk: previousTalk(state),
})
}
previousTalk: previousTalk(state)
});
};
}
export function alreadyTalked() {
......@@ -45,24 +44,22 @@ export function alreadyTalked() {
}
return dispatch({
type: ALREADY_TALKED,
category,
})
}
category
});
};
}
export function tick() {
return {
type: TICK,
timestamp: Date.now(),
}
timestamp: Date.now()
};
}
export function changeTimerLimit(value) {
return {
type: CHANGE_TIMER_LIMIT,
maxTime: value,
warnTime: value - Math.round(value / 5),
}
warnTime: value - Math.round(value / 5)
};
}
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import grey from '@material-ui/core/colors/grey';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import grey from "@material-ui/core/colors/grey";
import { defineMessages, FormattedMessage } from "react-intl";
const styles = {
button: {
color: grey[600],
},
color: grey[600]
}
};
const messages = defineMessages({
text: {
id: "alreadytalkedbutton.text",
defaultMessage: "This person has already spoken",
},
defaultMessage: "This person has already spoken"
}
});
class AlreadyTalkedButton extends Component {
render() {
const { classes } = this.props;
return (
......@@ -35,7 +28,7 @@ class AlreadyTalkedButton extends Component {
onClick={this.props.onClick}
className={classes.button}
disabled={this.props.disabled}
>
>
<FormattedMessage {...messages.text} />
</Button>
);
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
import ConnectedSpeakerButton from '../containers/ConnectedSpeakerButton';
import ConnectedAlreadyTalkedButton from '../containers/ConnectedAlreadyTalkedButton';
import ConnectedTimer from '../containers/ConnectedTimer';
import ConnectedTimerLimitEdit from '../containers/ConnectedTimerLimitEdit';
import ConnectedTicker from '../containers/ConnectedTicker';
import ConnectedStats from '../containers/ConnectedStats';
import Header from './Header';
import Footer from './Footer';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import CssBaseline from "@material-ui/core/CssBaseline";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
import ConnectedSpeakerButton from "../containers/ConnectedSpeakerButton";
import ConnectedAlreadyTalkedButton from "../containers/ConnectedAlreadyTalkedButton";
import ConnectedTimer from "../containers/ConnectedTimer";
import ConnectedTimerLimitEdit from "../containers/ConnectedTimerLimitEdit";
import ConnectedTicker from "../containers/ConnectedTicker";
import ConnectedStats from "../containers/ConnectedStats";
import Header from "./Header";
import Footer from "./Footer";
const styles = theme => ({
root: {
flexGrow: 1,
flexGrow: 1
},
buttons: {
marginBottom: "1em",
marginBottom: "1em"
},
timer: {
marginTop: "1em",
marginBottom: "1em",
marginBottom: "1em"
},
stats: {},
stats: {}
});
const messages = defineMessages({
title: {
id: "app.title",
defaultMessage: "Who's talking?",
defaultMessage: "Who's talking?"
},
dude: {
id: "app.dude",
defaultMessage: "A dude",
defaultMessage: "A dude"
},
notdude: {
id: "app.notdude",
defaultMessage: "Not a dude",
},
defaultMessage: "Not a dude"
}
});
class App extends Component {
render() {
const { classes } = this.props;
return (
......@@ -59,13 +52,14 @@ class App extends Component {
<Header />
<Grid item xs={12}>
<Grid container className={classes.buttons} justify="center" spacing={16}>
<Grid
container
className={classes.buttons}
justify="center"
spacing={16}
>
<Grid item xs={12}>
<Typography
variant="title"
component="h3"
align="center"
>
<Typography variant="title" component="h3" align="center">
<FormattedMessage {...messages.title} />
</Typography>
</Grid>
......@@ -74,19 +68,17 @@ class App extends Component {
category="man"
isNew={true}
text={this.props.intl.formatMessage(messages.dude)}
/>
/>
</Grid>
<Grid item>
<ConnectedSpeakerButton
category="other"
isNew={true}
text={this.props.intl.formatMessage(messages.notdude)}
/>
/>
</Grid>
<Grid item xs={12}>
<Typography
align="center"
>
<Typography align="center">
<ConnectedAlreadyTalkedButton />
</Typography>
</Grid>
......@@ -112,7 +104,6 @@ class App extends Component {
<Grid item xs={12}>
<ConnectedTimerLimitEdit />
</Grid>
</Grid>
<ConnectedTicker />
<Footer />
......
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { IntlProvider } from 'react-intl';
import configureStore from '../store';
import App from './App';
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { IntlProvider } from "react-intl";
import configureStore from "../store";
import App from "./App";
it('renders without crashing', () => {
const div = document.createElement('div');
it("renders without crashing", () => {
const div = document.createElement("div");
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
......@@ -14,6 +14,7 @@ it('renders without crashing', () => {
<App />
</IntlProvider>
</Provider>,
div);
div
);
ReactDOM.unmountComponentAtNode(div);
});
import React, { Component } from 'react';
import React, { Component } from "react";
class Duration extends Component {
render() {
const minutes = Math.trunc(this.props.value / 60);
const seconds = Math.trunc(this.props.value % 60);
const secondsText = String(seconds).padStart(2, "0");
return (
<span>
{`${minutes}:${secondsText}`}
</span>
);
return <span>{`${minutes}:${secondsText}`}</span>;
}
}
......
import React, { Component } from 'react';
import IconButton from '@material-ui/core/IconButton';
import Icon from '@material-ui/core/Icon';
import Tooltip from '@material-ui/core/Tooltip';
import {
injectIntl,
defineMessages,
} from 'react-intl';
import React, { Component } from "react";
import IconButton from "@material-ui/core/IconButton";
import Icon from "@material-ui/core/Icon";
import Tooltip from "@material-ui/core/Tooltip";
import { injectIntl, defineMessages } from "react-intl";
const messages = defineMessages({
save: {
id: "exportbutton.save",
defaultMessage: "Save",
defaultMessage: "Save"
},
date: {
id: "exportbutton.date",
defaultMessage: "Date",
defaultMessage: "Date"
},
timeMan: {
id: "exportbutton.timeMan",
defaultMessage: "Men speaking time",
defaultMessage: "Men speaking time"
},
timeOther: {
id: "exportbutton.timeOther",
defaultMessage: "Other speaking time",
defaultMessage: "Other speaking time"
},
speechesMan: {
id: "exportbutton.speechesMan",
defaultMessage: "Speeches by men",
defaultMessage: "Speeches by men"
},
speechesOther: {
id: "exportbutton.speechesOther",
defaultMessage: "Other speeches",
defaultMessage: "Other speeches"
},
speakersMan: {
id: "exportbutton.speakersMan",
defaultMessage: "Number of men speaking",
defaultMessage: "Number of men speaking"
},
speakersOther: {
id: "exportbutton.speakersOther",
defaultMessage: "Number of other speakers",
},
defaultMessage: "Number of other speakers"
}
});
class ExportButton extends Component {
makeData = () => {
const now = new Date();
const toCSVLine = (a) => (
a.map((v) => ('"' + v + '"')).join(",") + ";\n"
);
const toCSVLine = a => a.map(v => '"' + v + '"').join(",") + ";\n";
let data = [
toCSVLine([
this.props.intl.formatMessage(messages.date),
......@@ -59,7 +51,7 @@ class ExportButton extends Component {
this.props.intl.formatMessage(messages.speechesMan),
this.props.intl.formatMessage(messages.speechesOther),
this.props.intl.formatMessage(messages.speakersMan),
this.props.intl.formatMessage(messages.speakersOther),
this.props.intl.formatMessage(messages.speakersOther)
]),
toCSVLine([
now.toISOString(),
......@@ -68,17 +60,16 @@ class ExportButton extends Component {
this.props.speechesMan,
this.props.speechesOther,
this.props.speakersMan,
this.props.speakersOther,
]),
]
this.props.speakersOther
])
];
return data;
//return data.map((v) => ('"' + v + '"')).join(",") + ";";
};
}
onClick = (e) => {
onClick = e => {
e.preventDefault();
const blob = new Blob(this.makeData(), {type: 'text/csv'});
const blob = new Blob(this.makeData(), { type: "text/csv" });
const csvURL = window.URL.createObjectURL(blob);
window.open(csvURL);
// Allows setting the filename, but does not work on Firefox
......@@ -86,18 +77,15 @@ class ExportButton extends Component {
//tempLink.href = csvURL;
//tempLink.setAttribute('download', 'talk.csv');
//tempLink.click();
}
};
render() {
return (
<Tooltip
title={this.props.intl.formatMessage(messages.save)}
placement="left"
>
<IconButton
color="default"
onClick={this.onClick}
>
>
<IconButton color="default" onClick={this.onClick}>
<Icon>save_alt</Icon>
</IconButton>
</Tooltip>
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { defineMessages, FormattedMessage } from "react-intl";
const styles = theme => ({
root: {
borderTop: "1px solid grey",
marginTop: "5em",
paddingTop: "1em",
color: "grey",
},
color: "grey"
}
});
const messages = defineMessages({
text1: {
id: "footer.text1",
defaultMessage: "Very heavily inspired by",
defaultMessage: "Very heavily inspired by"
},
text2: {
id: "footer.text2",
defaultMessage: "Actually, it's the same thing with more stats.",
defaultMessage: "Actually, it's the same thing with more stats."
},
viewCode: {
id: "footer.viewCode",
defaultMessage: "See the code",
},
defaultMessage: "See the code"
}
});
class Footer extends Component {
render() {
const { classes } = this.props;
return (
<Grid container className={classes.root} justify="center">
<Grid item xs={12} className={classes.title}>
<Typography
variant="caption"
paragraph={true}
align="center"
>
<FormattedMessage {...messages.text1} /> {" "}
<a href="http://arementalkingtoomuch.com">AreMenTalkingTooMuch</a>. {" "}
<FormattedMessage {...messages.text2} /> {" "}
<Typography variant="caption" paragraph={true} align="center">
<FormattedMessage {...messages.text1} />{" "}
<a href="http://arementalkingtoomuch.com">AreMenTalkingTooMuch</a>.{" "}
<FormattedMessage {...messages.text2} />{" "}
<a href="https://gitlab.com/abompard/men-talk-too-much">
<FormattedMessage {...messages.viewCode} />
</a>.
</a>
.
</Typography>
</Grid>
</Grid>
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import {
defineMessages,
FormattedMessage,
} from 'react-intl';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Grid from "@material-ui/core/Grid";
import Typography from "@material-ui/core/Typography";
import { defineMessages, FormattedMessage } from "react-intl";
const styles = theme => ({
title: {
marginTop: "0.5em",
marginBottom: "1em",
},
marginBottom: "1em"
}
});
const messages = defineMessages({
title: {
id: "header.title",
defaultMessage: "Men talk too much, prove it.",
},
defaultMessage: "Men talk too much, prove it."
}
});
class Header extends Component {
render() {
const { classes } = this.props;
return (
<Grid item xs={12}>
<Typography
variant="display1"
align="center"
className={classes.title}
>
<FormattedMessage {...messages.title} />
</Typography>
<Typography variant="display1" align="center" className={classes.title}>
<FormattedMessage {...messages.title} />
</Typography>
</Grid>
);
}
......
import React, { Component } from 'react';
import Button from '@material-ui/core/Button';
import React, { Component } from "react";
import Button from "@material-ui/core/Button";
class SpeakerButton extends Component {
onClick = (e) => (
this.props.selected ? this.props.stopTalking() : this.props.startTalking()
)
onClick = e =>
this.props.selected ? this.props.stopTalking() : this.props.startTalking();
render() {
return (
<div className="SpeakerButton">
<Button
variant={this.props.selected ? "contained" : "outlined" }
variant={this.props.selected ? "contained" : "outlined"}
color="primary"
size="large"
onClick={this.onClick}
>
>
{this.props.text}
</Button>
</div>
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import Icon from '@material-ui/core/Icon';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
import Duration from './Duration';
import ExportButton from './ExportButton';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import List from "@material-ui/core/List";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import Icon from "@material-ui/core/Icon";
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
import Duration from "./Duration";
import ExportButton from "./ExportButton";
const styles = {
root: {
marginTop: "2em",
},
marginTop: "2em"
}
};
const messages = defineMessages({
nobody: {
id: "stats.nobody",
defaultMessage: "Nobody has spoken yet.",
defaultMessage: "Nobody has spoken yet."
},
header: {
id: "stats.header",
defaultMessage: "Men have talked:",
defaultMessage: "Men have talked:"
},
timePercent: {
id: "stats.timePercent",
defaultMessage: "{value}% of the time",
defaultMessage: "{value}% of the time"
},
speechesPercent: {
id: "stats.speechesPercent",
defaultMessage: "{value}% of the opportunities",
defaultMessage: "{value}% of the opportunities"
},
speakersPercent: {
id: "stats.speakersPercent",
defaultMessage: "{value}% of the speakers",
defaultMessage: "{value}% of the speakers"
},
speechesDuration: {
id: "stats.speechesDuration",
defaultMessage: "Mean speech duration:",
defaultMessage: "Mean speech duration:"
},
forOthers: {
id: "stats.forOthers",
defaultMessage: "For others:",
},
defaultMessage: "For others:"
}
});
class Stats extends Component {
render() {
const { classes } = this.props;
return (
......@@ -67,16 +59,31 @@ class Stats extends Component {
}
class StatsValues extends Component {
render() {
const totalSpokenTime = this.props.stats.man.duration + this.props.stats.other.duration;
const totalSpeeches = this.props.stats.man.speeches + this.props.stats.other.speeches;
const totalSpeakers = this.props.stats.man.speakers + this.props.stats.other.speakers;
const timePercent = totalSpokenTime === 0 ? 0 : Math.round(this.props.stats.man.duration / totalSpokenTime * 100);
const speechesPercent = totalSpeeches === 0 ? 0 : Math.round(this.props.stats.man.speeches / totalSpeeches * 100);
const speakersPercent = totalSpeakers === 0 ? 0 : Math.round(this.props.stats.man.speakers / totalSpeakers * 100);
const manMeanTime = Math.round(this.props.stats.man.duration / this.props.stats.man.speeches / 1000)
const otherMeanTime = Math.round(this.props.stats.other.duration / this.props.stats.other.speeches / 1000)
const totalSpokenTime =
this.props.stats.man.duration + this.props.stats.other.duration;
const totalSpeeches =
this.props.stats.man.speeches + this.props.stats.other.speeches;
const totalSpeakers =
this.props.stats.man.speakers + this.props.stats.other.speakers;
const timePercent =
totalSpokenTime === 0
? 0
: Math.round((this.props.stats.man.duration / totalSpokenTime) * 100);
const speechesPercent =
totalSpeeches === 0
? 0
: Math.round((this.props.stats.man.speeches / totalSpeeches) * 100);
const speakersPercent =
totalSpeakers === 0
? 0
: Math.round((this.props.stats.man.speakers / totalSpeakers) * 100);
const manMeanTime = Math.round(
this.props.stats.man.duration / this.props.stats.man.speeches / 1000
);
const otherMeanTime = Math.round(
this.props.stats.other.duration / this.props.stats.other.speeches / 1000
);
if (totalSpeakers === 0) {
return (
......@@ -87,33 +94,25 @@ class StatsValues extends Component {
}
return (
<div>
<Typography
variant="subheading"
component="h3"
>
<Typography variant="subheading" component="h3">
<FormattedMessage {...messages.header} />
</Typography>
<List
dense
disablePadding
>
<List dense disablePadding>
<ListItem>
<ListItemIcon>
<Icon>schedule</Icon>
</ListItemIcon>
<ListItemText
primary={this.props.intl.formatMessage(messages.timePercent, {value: timePercent})}
primary={this.props.intl.formatMessage(messages.timePercent, {
value: timePercent
})}
secondary={
<span>
(
<Duration value={this.props.stats.man.duration / 1000} />
/
<Duration value={totalSpokenTime / 1000} />
)
(<Duration value={this.props.stats.man.duration / 1000} />/
<Duration value={totalSpokenTime / 1000} />)
</span>
}
/>
/>
</ListItem>
<ListItem>
......@@ -121,9 +120,11 @@ class StatsValues extends Component {
<Icon>pan_tool</Icon>
</ListItemIcon>
<ListItemText
primary={this.props.intl.formatMessage(messages.speechesPercent, {value: speechesPercent})}
primary={this.props.intl.formatMessage(messages.speechesPercent, {
value: speechesPercent
})}
secondary={`(${this.props.stats.man.speeches}/${totalSpeeches})`}
/>
/>
</ListItem>
<ListItem>
......@@ -131,9 +132,11 @@ class StatsValues extends Component {
<Icon>face</Icon>
</ListItemIcon>
<ListItemText
primary={this.props.intl.formatMessage(messages.speakersPercent, {value: speakersPercent})}
primary={this.props.intl.formatMessage(messages.speakersPercent, {
value: speakersPercent
})}
secondary={`(${this.props.stats.man.speakers}/${totalSpeakers})`}
/>
/>
</ListItem>
<ListItem>
......@@ -143,26 +146,27 @@ class StatsValues extends Component {
<ListItemText
primary={
<span>
<FormattedMessage {...messages.speechesDuration} /> {" "}
{ this.props.stats.man.speeches > 0 ?
<FormattedMessage {...messages.speechesDuration} />{" "}
{this.props.stats.man.speeches > 0 ? (
<Duration value={manMeanTime} />
: "?"
}
) : (
"?"
)}
</span>
}
secondary={
<span>
(<FormattedMessage {...messages.forOthers} /> {" "}
{ this.props.stats.other.speeches > 0 ?
<Duration value={otherMeanTime} />
: "?"
}
(<FormattedMessage {...messages.forOthers} />{" "}
{this.props.stats.other.speeches > 0 ? (
<Duration value={otherMeanTime} />
) : (
"?"
)}
)
</span>
}
/>
/>
</ListItem>
</List>
<Typography align="right">
......@@ -173,7 +177,7 @@ class StatsValues extends Component {
speechesOther={this.props.stats.other.speeches}
speakersMan={this.props.stats.man.speakers}
speakersOther={this.props.stats.other.speakers}
/>
/>
</Typography>
</div>
);
......
import { Component } from 'react';
import { Component } from "react";
class Ticker extends Component {
state = {
timer: null,
timer: null
};
componentWillUnmount() {
......@@ -15,7 +13,7 @@ class Ticker extends Component {
if (this.props.running) {
if (prevProps.startedAt !== this.props.startedAt) {
// restart the timer, don't just update
this.stop()
this.stop();
}
this.start();
} else {
......@@ -23,9 +21,7 @@ class Ticker extends Component {
}
}
isCounting = () => (
this.state.timer !== null
)
isCounting = () => this.state.timer !== null;
start = () => {
if (this.isCounting()) {
......@@ -34,7 +30,7 @@ class Ticker extends Component {
this.setState({
timer: setInterval(this.tick, 1000)
});
}
};
stop = () => {
if (!this.isCounting()) {
......@@ -42,16 +38,16 @@ class Ticker extends Component {
}
clearInterval(this.state.timer);
this.setState({
timer: null,
timer: null
});
}
};
tick = () => {
if (!this.isCounting() || !this.props.running) {
return;
}
this.props.tick();
}
};
render() {
return null;
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import LinearProgress from '@material-ui/core/LinearProgress';
import orange from '@material-ui/core/colors/orange';
import Duration from './Duration';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Card from "@material-ui/core/Card";
import CardContent from "@material-ui/core/CardContent";
import Typography from "@material-ui/core/Typography";
import LinearProgress from "@material-ui/core/LinearProgress";
import orange from "@material-ui/core/colors/orange";
import Duration from "./Duration";
const styles = {
warning: {
color: orange[600],
color: orange[600]
},
barWarning: {
backgroundColor: orange[600],
backgroundColor: orange[600]
},
barWarningBg: {
backgroundColor: orange[100],
backgroundColor: orange[100]
},
card: {
width: 200,
width: 200
},
cardContent: {
paddingBottom: "16px !important",
paddingBottom: "16px !important"
},
progress: {
marginTop: "15px",
marginBottom: "-5px",
marginBottom: "-5px"
}
};
class Timer extends Component {
render() {
const { classes } = this.props;
const counter = Math.trunc(this.props.ticker / 1000);
......@@ -48,11 +45,11 @@ class Timer extends Component {
barColor = "secondary";
barClasses = {
barColorSecondary: classes.barWarning,
colorSecondary: classes.barWarningBg,
colorSecondary: classes.barWarningBg
};
}
}
const completed = Math.min(counter / this.props.maxTime * 100, 100);
const completed = Math.min((counter / this.props.maxTime) * 100, 100);
return (
<div className="Timer">
<Card className={classes.card}>
......@@ -63,21 +60,20 @@ class Timer extends Component {
align="center"
color={color}
classes={{
colorTextSecondary: classes.warning,
colorTextSecondary: classes.warning
}}
>
>
<Duration value={counter} />
</Typography>
{ this.props.maxTime ?
{this.props.maxTime ? (
<LinearProgress
color={barColor}
variant="determinate"
value={completed}
className={classes.progress}
classes={barClasses}
/>
: null
}
/>
) : null}
</CardContent>
</Card>
</div>
......
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Modal from '@material-ui/core/Modal';
import Button from '@material-ui/core/Button';
import Icon from '@material-ui/core/Icon';
import TextField from '@material-ui/core/TextField';
import {
injectIntl,
defineMessages,
FormattedMessage,
} from 'react-intl';
import React, { Component } from "react";
import { withStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
import Modal from "@material-ui/core/Modal";
import Button from "@material-ui/core/Button";
import Icon from "@material-ui/core/Icon";
import TextField from "@material-ui/core/TextField";
import { injectIntl, defineMessages, FormattedMessage } from "react-intl";
const styles = theme => ({
paper: {
position: 'absolute',
position: "absolute",
width: theme.spacing.unit * 50,
backgroundColor: theme.palette.background.paper,
boxShadow: theme.shadows[5],
......@@ -22,61 +17,58 @@ const styles = theme => ({
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
border: "1px solid black",
border: "1px solid black"
},
buttonField: {
marginTop: "1em",
marginTop: "1em"
},
title: {
marginBottom: "1em",
marginBottom: "1em"
},
textField: {
marginLeft: theme.spacing.unit,
marginRight: theme.spacing.unit,
width: 70,
},
width: 70
}
});
const messages = defineMessages({
nolimit: {
id: "timerlimitedit.nolimit",
defaultMessage: "no limit",
defaultMessage: "no limit"
},
buttontext: {
id: "timerlimitedit.buttontext",
defaultMessage: "{min}:{sec} max",
defaultMessage: "{min}:{sec} max"
},
modaltitle: {
id: "timerlimitedit.modaltitle",
defaultMessage: "Maximum speech time",
defaultMessage: "Maximum speech time"
},
minutes: {
id: "timerlimitedit.minutes",
defaultMessage: "Minutes",
defaultMessage: "Minutes"
},
seconds: {
id: "timerlimitedit.seconds",
defaultMessage: "Seconds",
},
defaultMessage: "Seconds"
}
});
class TimerLimitEdit extends Component {
state = {
open: false,
open: false
};
handleOpen = () => {
this.setState({open: true});
this.setState({ open: true });
};
handleClose = () => {
this.setState({ open: false });
};
handleChange = (e) => {
handleChange = e => {
let maxTime;
let value = e.target.value ? parseInt(e.target.value, 10) : 0;
if (e.target.name === "minutes") {
......@@ -90,13 +82,9 @@ class TimerLimitEdit extends Component {
this.props.onChange(maxTime);
};
getMinutes = () => (
Math.trunc(this.props.maxTime / 60)
);
getMinutes = () => Math.trunc(this.props.maxTime / 60);
getSeconds = () => (
this.props.maxTime % 60
);
getSeconds = () => this.props.maxTime % 60;
render() {
const { classes } = this.props;
......@@ -105,19 +93,16 @@ class TimerLimitEdit extends Component {
return (
<div>
<Typography align="center" className={classes.buttonField}>
<Button
onClick={this.handleOpen}
size="small"
>
<Icon style={{marginRight: "0.3em"}}>alarm</Icon>
{ (minutes === "0" && seconds === "00") ?
<Button onClick={this.handleOpen} size="small">
<Icon style={{ marginRight: "0.3em" }}>alarm</Icon>
{minutes === "0" && seconds === "00" ? (
<FormattedMessage {...messages.nolimit} />
:
) : (
<FormattedMessage
{...messages.buttontext}
values={{min: minutes, sec:seconds}}
/>
}
values={{ min: minutes, sec: seconds }}
/>
)}
</Button>
</Typography>
<Modal
......@@ -130,7 +115,7 @@ class TimerLimitEdit extends Component {
variant="title"
id="modal-title"
className={classes.title}
>
>
<FormattedMessage {...messages.modaltitle} />
</Typography>
<TextField
......@@ -140,11 +125,11 @@ class TimerLimitEdit extends Component {
type="number"
className={classes.textField}
InputLabelProps={{
shrink: true,
shrink: true
}}
value={minutes}
onChange={this.handleChange}
/>
/>
<TextField
id="maxseconds"
name="seconds"
......@@ -152,14 +137,14 @@ class TimerLimitEdit extends Component {
type="number"
className={classes.textField}
InputLabelProps={{
shrink: true,
shrink: true
}}
inputProps={{
step: 10,
step: 10
}}
value={seconds}
onChange={this.handleChange}
/>
/>
</div>
</Modal>
</div>
......
import { connect } from 'react-redux'
import { alreadyTalked } from '../actions'
import AlreadyTalkedButton from '../components/AlreadyTalkedButton'
import { connect } from "react-redux";
import { alreadyTalked } from "../actions";
import AlreadyTalkedButton from "../components/AlreadyTalkedButton";
const mapStateToProps = (state, ownProps) => {
return {
disabled: (
state.current.category === null
|| state.current.alreadyTalked
|| state.talked[state.current.category].speakers === 0
),
}
}
disabled:
state.current.category === null ||
state.current.alreadyTalked ||
state.talked[state.current.category].speakers === 0
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => dispatch(alreadyTalked()),
}
}
onClick: () => dispatch(alreadyTalked())
};
};
const ConnectedAlreadyTalkedButton = connect(
mapStateToProps,
mapDispatchToProps
)(AlreadyTalkedButton)
)(AlreadyTalkedButton);
export default ConnectedAlreadyTalkedButton
export default ConnectedAlreadyTalkedButton;
import { connect } from 'react-redux'
import { startTalking, stopTalking } from '../actions'
import SpeakerButton from '../components/SpeakerButton'
import { connect } from "react-redux";
import { startTalking, stopTalking } from "../actions";
import SpeakerButton from "../components/SpeakerButton";
const mapStateToProps = (state, ownProps) => {
return {
selected: ownProps.category === state.current.category
}
}
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
startTalking: () => dispatch(startTalking(ownProps.category)),
stopTalking: () => dispatch(stopTalking()),
}
}
stopTalking: () => dispatch(stopTalking())
};
};
const ConnectedSpeakerButton = connect(
mapStateToProps,
mapDispatchToProps
)(SpeakerButton)
)(SpeakerButton);
export default ConnectedSpeakerButton
export default ConnectedSpeakerButton;
import { connect } from 'react-redux'
import Stats from '../components/Stats'
import { connect } from "react-redux";
import Stats from "../components/Stats";
const mapStateToProps = (state, ownProps) => {
let stats = {
......@@ -9,18 +8,16 @@ const mapStateToProps = (state, ownProps) => {
},
other: {
...state.talked.other
},
}
};
if (state.current.ticker !== null) {
stats[state.current.category].duration += state.current.ticker;
stats[state.current.category].speeches += 1;
stats[state.current.category].speakers += 1;
}
return {stats};
}
return { stats };
};
const ConnectedStats = connect(
mapStateToProps,
)(Stats)
const ConnectedStats = connect(mapStateToProps)(Stats);
export default ConnectedStats
export default ConnectedStats;
import { connect } from 'react-redux'
import { tick } from '../actions'
import Ticker from '../components/Ticker'
import { connect } from "react-redux";
import { tick } from "../actions";
import Ticker from "../components/Ticker";
const mapStateToProps = (state, ownProps) => {
return {
running: state.current.ticker !== null,
startedAt: state.current.startedAt,
}
}
startedAt: state.current.startedAt
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
tick: () => dispatch(tick()),
}
}
tick: () => dispatch(tick())
};
};
const ConnectedTicker = connect(
mapStateToProps,
mapDispatchToProps
)(Ticker)
)(Ticker);
export default ConnectedTicker
export default ConnectedTicker;
import { connect } from 'react-redux'
import Timer from '../components/Timer'
import { connect } from "react-redux";
import Timer from "../components/Timer";
const mapStateToProps = (state, ownProps) => {
return {
ticker: state.current.ticker,
maxTime: state.limits.maxTime,
warnTime: state.limits.warnTime,
}
}
warnTime: state.limits.warnTime
};
};
const ConnectedTimer = connect(
mapStateToProps,
)(Timer)
const ConnectedTimer = connect(mapStateToProps)(Timer);
export default ConnectedTimer
export default ConnectedTimer;
import { connect } from 'react-redux'
import { changeTimerLimit } from '../actions'
import TimerLimitEdit from '../components/TimerLimitEdit'
import { connect } from "react-redux";
import { changeTimerLimit } from "../actions";
import TimerLimitEdit from "../components/TimerLimitEdit";
const mapStateToProps = (state, ownProps) => {
return {
maxTime: state.limits.maxTime,
}
}
maxTime: state.limits.maxTime
};
};
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onChange: (value) => dispatch(changeTimerLimit(value)),
}
}
onChange: value => dispatch(changeTimerLimit(value))
};
};
const ConnectedTimerLimitEdit = connect(
mapStateToProps,
mapDispatchToProps,
)(TimerLimitEdit)
mapDispatchToProps
)(TimerLimitEdit);
export default ConnectedTimerLimitEdit
export default ConnectedTimerLimitEdit;
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import configureStore from './store';
import App from './components/App';
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import configureStore from "./store";
import App from "./components/App";
//import registerServiceWorker from './registerServiceWorker';
import { addLocaleData } from 'react-intl';
import en from 'react-intl/locale-data/en';
import fr from 'react-intl/locale-data/fr';
import { IntlProvider } from 'react-intl';
import { addLocaleData } from "react-intl";
import en from "react-intl/locale-data/en";
import fr from "react-intl/locale-data/fr";
import { IntlProvider } from "react-intl";
import messages_fr from "./locales/fr.json";
addLocaleData([...en, ...fr]);
const localeData = {
'fr': messages_fr,
fr: messages_fr
};
// Define user's language. Different browsers have the user locale defined
......@@ -34,17 +34,15 @@ const messages =
localeData[language] ||
localeData.en;
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<IntlProvider locale={navigator.language} messages={messages}>
<App />
</IntlProvider>
</Provider>,
document.getElementById('root')