diff --git a/.gitignore b/.gitignore index d30f40e..231d684 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. +.idea # dependencies /node_modules diff --git a/package.json b/package.json index c34f829..9da9ace 100644 --- a/package.json +++ b/package.json @@ -2,70 +2,74 @@ "name": "mob-timer", "version": "1.2.0", "dependencies": { - "@babel/core": "^7.0.0-beta.46", - "@babel/preset-env": "^7.0.0-beta.46", - "@babel/preset-react": "^7.0.0-beta.46", - "autoprefixer": "8.4.1", - "babel-jest": "^22.4.3", + "@babel/core": "^7.0.0-beta.54", + "@babel/preset-env": "^7.0.0-beta.54", + "@babel/preset-react": "^7.0.0-beta.54", + "autoprefixer": "9.0.0", + "babel-jest": "^23.4.0", "babel-loader": "^8.0.0-beta.2", "case-sensitive-paths-webpack-plugin": "2.1.2", "chalk": "^2.4.1", - "css-loader": "0.28.11", - "dotenv": "5.0.1", + "css-loader": "1.0.0", + "dotenv": "6.0.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", "file-loader": "1.1.11", - "fork-ts-checker-webpack-plugin": "^0.4.1", - "fs-extra": "6.0.0", + "fork-ts-checker-webpack-plugin": "^0.4.3", + "fs-extra": "7.0.0", "html-webpack-plugin": "^3.2.0", "interpolate-html-plugin": "^3.0.0", - "jest": "22.4.3", - "node-sass": "^4.9.0", + "jest": "23.4.1", + "node-sass": "^4.9.2", "normalize.css": "^8.0.0", "object-assign": "4.1.1", - "postcss-flexbugs-fixes": "3.3.1", - "postcss-loader": "2.1.4", + "postcss-flexbugs-fixes": "4.0.0", + "postcss-loader": "2.1.6", "promise": "8.0.1", "raf": "3.4.0", - "react": "^16.3.2", + "react": "^16.4.1", "react-dev-utils": "^6.0.0-next.b2fd8db8", - "react-dnd": "^2.6.0", - "react-dnd-multi-backend": "^3.1.2", - "react-dom": "^16.3.2", + "react-dnd": "^5.0.0", + "react-dnd-html5-backend": "^5.0.1", + "react-dnd-multi-backend": "^3.1.0", + "react-dom": "^16.4.1", + "react-hot-loader": "^4.3.3", "react-keydown": "^1.9.7", - "react-md": "^1.3.0", + "react-md": "^1.4.2", "react-redux": "^5.0.7", - "redux": "^3.7.2", - "redux-observable": "^0.18.0", - "rxjs": "^5.5.10", - "sass-loader": "^7.0.1", + "redux": "^4.0.0", + "redux-observable": "^1.0.0", + "rxjs": "^6.2.2", + "sass-loader": "^7.0.3", "source-map-loader": "^0.2.3", "style-loader": "0.21.0", - "styled-components": "^3.2.6", + "styled-components": "^3.3.3", "sw-precache-webpack-plugin": "0.11.5", - "ts-loader": "^4.2.0", - "uglifyjs-webpack-plugin": "^1.2.5", + "ts-loader": "^4.4.2", + "typesafe-actions": "^2.0.4", + "uglifyjs-webpack-plugin": "^1.2.7", "url-loader": "1.0.1", - "webpack": "4.6.0", - "webpack-manifest-plugin": "2.0.2" + "webpack": "4.16.1", + "webpack-manifest-plugin": "2.0.3" }, "devDependencies": { - "@types/jest": "^22.2.3", - "@types/react": "16.3.13", - "@types/react-dnd": "^2.0.36", + "@types/jest": "^23.3.0", + "@types/node": "^10.5.2", + "@types/react": "16.4.6", "@types/react-dnd-multi-backend": "^3.0.1", - "@types/react-dom": "^16.0.5", - "@types/react-redux": "5.0.19", - "gh-pages": "^1.1.0", - "husky": "^1.0.0-rc.2", - "lint-staged": "^7.0.5", - "prettier": "^1.12.1", - "ts-jest": "22.4.4", - "tsconfig-paths-webpack-plugin": "^3.0.4", - "tslint": "^5.9.1", - "tslint-config-prettier": "^1.12.0", - "tslint-config-standard": "^7.0.0", - "tslint-react": "^3.5.1", - "typescript": "^2.8.3", + "@types/react-dom": "^16.0.6", + "@types/react-hot-loader": "^4.1.0", + "@types/react-redux": "^6.0.4", + "gh-pages": "^1.2.0", + "husky": "^0.14.3", + "lint-staged": "^7.2.0", + "prettier": "^1.13.7", + "ts-jest": "23.0.1", + "tsconfig-paths-webpack-plugin": "^3.2.0", + "tslint": "^5.11.0", + "tslint-config-prettier": "^1.14.0", + "tslint-config-standard": "^7.1.0", + "tslint-react": "^3.6.0", + "typescript": "^2.9.2", "webpack-dev-server": "3.1.4" }, "homepage": "https://beeequeue.github.io/mob-timer", @@ -76,17 +80,22 @@ } }, "jest": { - "collectCoverageFrom": ["src/**/*.{js,jsx,ts,tsx}"], - "setupFiles": ["/config/polyfills.js"], - "testMatch": ["/src/**/?(*.)(test).(j|t)s?(x)"], + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}" + ], + "setupFiles": [ + "/config/polyfills.js" + ], + "testMatch": [ + "/src/**/?(*.)(test).(j|t)s?(x)" + ], "testEnvironment": "node", "testURL": "http://localhost", "transform": { "^.+\\.(js|jsx)$": "/node_modules/babel-jest", "^.+\\.tsx?$": "/config/jest/typescriptTransform.js", "^.+\\.css$": "/config/jest/cssTransform.js", - "^(?!.*\\.(js|jsx|mjs|css|json)$)": - "/config/jest/fileTransform.js" + "^(?!.*\\.(js|jsx|mjs|css|json)$)": "/config/jest/fileTransform.js" }, "transformIgnorePatterns": [ "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$" @@ -95,7 +104,13 @@ "^react-native$": "react-native-web", "@state/(.*)": "/src/state/$1" }, - "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"], + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json" + ], "globals": { "ts-jest": { "tsConfigFile": "tsconfig.test.json" @@ -103,7 +118,10 @@ } }, "lint-staged": { - "*.{ts,tsx,json}": ["yarn format", "git add"] + "*.{ts,tsx,json}": [ + "yarn format", + "git add" + ] }, "private": true, "scripts": { diff --git a/src/App.tsx b/src/App.tsx index b107154..cf71b61 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import { compose } from 'redux' import { connect } from 'react-redux' @@ -6,12 +7,13 @@ import keydown from 'react-keydown/es' import styled from 'styled-components' import { startTimer, stopTimer, setTime } from '@state/actions/timerActions' + +import { Time } from './time' +import { IRootState } from './state' import { TimerContainer } from './containers/Timer' import { UserContainer } from './containers/Users' // import { GitHubLink } from './components/GitHubLink' import { multiBackend } from './utils/dragDropContext' -import { Time } from './time' -import { IState } from './state' const Container = styled.div` font-family: 'Roboto', sans-serif; @@ -30,22 +32,16 @@ interface IStateProps { duration: Time } -interface IDispatchProps { - startTimer: typeof startTimer - stopTimer: typeof stopTimer - setTime: typeof setTime -} - -const mapState = ({ timer }: IState): IStateProps => ({ +const mapState = ({ timer }: IRootState): IStateProps => ({ counting: timer.counting, duration: timer.duration, }) -const mapActions: IDispatchProps = { startTimer, stopTimer, setTime } +const mapActions = { startTimer, stopTimer, setTime } +type DispatchProps = typeof mapActions -export class AppComponent extends React.Component< - IStateProps & IDispatchProps -> { +@DragDropContext(multiBackend) +export class AppComponent extends React.Component { constructor(props: any) { super(props) @@ -78,6 +74,9 @@ export class AppComponent extends React.Component< } export const App = compose( - connect(mapState, mapActions), - DragDropContext(multiBackend) + hot(module), + connect( + mapState, + mapActions + ) )(AppComponent) diff --git a/src/components/AddUserDialog.tsx b/src/components/AddUserDialog.tsx index 7303926..143f897 100644 --- a/src/components/AddUserDialog.tsx +++ b/src/components/AddUserDialog.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import keydown from 'react-keydown/es' import { Button } from 'react-md/lib/Buttons' @@ -16,7 +17,7 @@ interface IState { readonly names: ReadonlyArray } -export class AddUserDialog extends React.PureComponent { +class AddUserDialogComponent extends React.PureComponent { constructor(props: IProps) { super(props) @@ -57,7 +58,7 @@ export class AddUserDialog extends React.PureComponent { this.submit() } - private onInputKeyDown = (e: React.KeyboardEvent) => { + private onInputKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === 13) return this.submit() } @@ -65,8 +66,8 @@ export class AddUserDialog extends React.PureComponent { this.setState({ value }) } - public selectName = (name: string) => { - this.setState({ value: name }) + public selectName = (name: React.ReactText) => { + this.setState({ value: name as string }) } private actions = [ @@ -101,3 +102,5 @@ export class AddUserDialog extends React.PureComponent { ) } } + +export const AddUserDialog = hot(module)(AddUserDialogComponent) diff --git a/src/components/Countdown.tsx b/src/components/Countdown.tsx index bac1e9f..9162b3c 100644 --- a/src/components/Countdown.tsx +++ b/src/components/Countdown.tsx @@ -1,6 +1,8 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' -import styled, { StyledComponentClass } from 'styled-components' import { Button, ButtonProps } from 'react-md/lib/Buttons' +import styled, { StyledComponentClass } from 'styled-components' + import { Time } from '../time' const Container = styled.div` @@ -47,8 +49,8 @@ interface IProps { onChangeTime: (time: Time) => void } -export class Countdown extends React.PureComponent { - private changeMinutes = (e: React.MouseEvent) => { +class CountdownComponent extends React.PureComponent { + private changeMinutes = (e: React.MouseEvent) => { const { time, onChangeTime } = this.props const newMinutes = time.minutes + Number(e.currentTarget.textContent) @@ -62,7 +64,7 @@ export class Countdown extends React.PureComponent { onChangeTime(newTime) } - private changeSeconds = (e: React.MouseEvent) => { + private changeSeconds = (e: React.MouseEvent) => { const { time, onChangeTime } = this.props let newSeconds = time.seconds + Number(e.currentTarget.textContent) @@ -112,3 +114,5 @@ export class Countdown extends React.PureComponent { ) } } + +export const Countdown = hot(module)(CountdownComponent) diff --git a/src/components/GitHubLink.tsx b/src/components/GitHubLink.tsx index d654cd4..d3ed4fd 100644 --- a/src/components/GitHubLink.tsx +++ b/src/components/GitHubLink.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import styled from 'styled-components' @@ -13,8 +14,10 @@ const Icon = styled.img` background: #eee; ` -export const GitHubLink: React.SFC = () => ( +export const GitHubLinkComponent: React.SFC = () => ( ) + +export const GitHubLink = hot(module)(GitHubLinkComponent) diff --git a/src/components/KeyboardShortcutsDialog.tsx b/src/components/KeyboardShortcutsDialog.tsx index d5ad4b5..3fc5818 100644 --- a/src/components/KeyboardShortcutsDialog.tsx +++ b/src/components/KeyboardShortcutsDialog.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import { DialogContainer } from 'react-md/lib/Dialogs' import { FontIcon } from 'react-md/lib/FontIcons' @@ -30,7 +31,7 @@ interface IProps { readonly hide: () => void } -export class KeyboardShortcutsDialog extends React.PureComponent { +class KeyboardShortcutsDialogComponent extends React.PureComponent { private actions = [{ children: 'Back', onClick: this.props.hide }] public render() { @@ -53,3 +54,5 @@ export class KeyboardShortcutsDialog extends React.PureComponent { ) } } + +export const KeyboardShortcutsDialog = hot(module)(KeyboardShortcutsDialogComponent) diff --git a/src/components/User.tsx b/src/components/User.tsx index 5970441..33f7b87 100644 --- a/src/components/User.tsx +++ b/src/components/User.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import { findDOMNode } from 'react-dom' import { @@ -31,7 +32,7 @@ const target: DropTargetSpec = { hover(props, monitor, component) { if (!monitor || !component) return - const dragIndex = (monitor.getItem() as any).index + const dragIndex = monitor.getItem().index const hoverIndex = props.index // Don't replace items with themselves @@ -51,7 +52,7 @@ const target: DropTargetSpec = { const offsetNeeded = elementHeight / 8 // Determine mouse position - const clientOffset = monitor.getClientOffset() + const clientOffset = monitor.getClientOffset() || { x: 0, y: 0 } // Get pixels to the top const hoverClientY = clientOffset.y - hoverBoundingRect.top @@ -72,11 +73,11 @@ const target: DropTargetSpec = { // Time to actually perform the action props.moveUser(dragIndex, hoverIndex) - ;(monitor.getItem() as any).index = hoverIndex + monitor.getItem().index = hoverIndex }, } -const source: DragSourceSpec = { +const source: DragSourceSpec> = { beginDrag(props) { return { index: props.index, user: props.user } }, @@ -100,7 +101,7 @@ const sourceCollect = ( @DropTarget('user', target, targetCollect) @DragSource('user', source, sourceCollect) -export class DraggableUser extends React.PureComponent { +class DraggableUserComponent extends React.PureComponent { public render() { const { connectDropTarget, connectDragSource, index } = this.props as any @@ -161,3 +162,5 @@ export class DraggableUser extends React.PureComponent { ) } } + +export const DraggableUser = hot(module)(DraggableUserComponent) diff --git a/src/components/UserList.tsx b/src/components/UserList.tsx index b245a18..0736d3c 100644 --- a/src/components/UserList.tsx +++ b/src/components/UserList.tsx @@ -1,3 +1,4 @@ +import { hot } from 'react-hot-loader' import * as React from 'react' import styled, { StyledComponentClass } from 'styled-components' import { List } from 'react-md/lib/Lists' @@ -69,7 +70,7 @@ interface IState { dialogVisible: boolean } -export class UserList extends React.PureComponent { +class UserListComponent extends React.PureComponent { public state = { dialogVisible: false, } @@ -136,3 +137,5 @@ export class UserList extends React.PureComponent { ) } } + +export const UserList = hot(module)(UserListComponent) diff --git a/src/containers/Timer.tsx b/src/containers/Timer.tsx index 8845662..1ac2f8d 100644 --- a/src/containers/Timer.tsx +++ b/src/containers/Timer.tsx @@ -9,7 +9,7 @@ import { stopTimer, countDownOneSecond, } from '@state/actions/timerActions' -import { IState as IRootState } from '@state/index' +import { IRootState } from '@state/index' import { Time } from '../time' import { Countdown } from '../components/Countdown' import { KeyboardShortcutsDialog } from '../components/KeyboardShortcutsDialog' diff --git a/src/containers/Users.tsx b/src/containers/Users.tsx index d50fb1a..22185b3 100644 --- a/src/containers/Users.tsx +++ b/src/containers/Users.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { connect } from 'react-redux' -import { IState } from '@state/index' +import { IRootState } from '@state/index' import { setOrder, addUser, @@ -25,7 +25,7 @@ interface IActionProps { toggleHideUserList: typeof toggleHideUserList } -const mapState = ({ users }: IState): IStateProps => ({ +const mapState = ({ users }: IRootState): IStateProps => ({ users: users.list, activeUser: users.activeUser, hideUserList: users.hideUserList, diff --git a/src/index.tsx b/src/index.tsx index 3e02b6a..6534e1b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,19 +2,10 @@ import * as React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' -import 'rxjs/add/operator/do' -import 'rxjs/add/operator/filter' -import 'rxjs/add/operator/mapTo' -import 'rxjs/add/operator/mergeMap' -import 'rxjs/add/operator/ignoreElements' -import 'rxjs/add/operator/takeUntil' -import 'rxjs/add/operator/switchMap' -import 'rxjs/add/observable/interval' - import 'normalize.css' import './index.scss' -import { store, epicMiddleware } from '@state/index' +import { store } from '@state/index' import { requestPermission } from './utils/notifications' import { App } from './App' // import { register as registerSW } from './registerServiceWorker' @@ -30,25 +21,4 @@ render( document.getElementById('root') as HTMLElement ) -if ((module as any).hot) { - ;(module as any).hot.accept('./App', () => { - const { App: NextApp } = require('./App') - - render( - - - - - , - document.getElementById('root') - ) - }) - ;(module as any).hot.accept('@state/index', () => { - const { reducers: nextReducers, epics: nextEpics } = require('@state/index') - - store.replaceReducer(nextReducers) - epicMiddleware.replaceEpic(nextEpics) - }) -} - // registerSW() diff --git a/src/state/actions/constants.ts b/src/state/actions/constants.ts new file mode 100644 index 0000000..8efb019 --- /dev/null +++ b/src/state/actions/constants.ts @@ -0,0 +1,13 @@ +export const SET_TIME = 'SET_TIME' +export const START_TIMER = 'START_TIMER' +export const STOP_TIMER = 'STOP_TIMER' +export const COUNT_DOWN_ONE_SECOND = 'COUNT_DOWN_ONE_SECOND' +export const COUNT_DOWN_FINISHED = 'COUNT_DOWN_FINISHED' +export const ADD_NOTIFICATION = 'ADD_NOTIFICATION' + +export const ADD_USER = 'ADD_USER' +export const REMOVE_USER = 'REMOVE_USER' +export const SET_ORDER = 'SET_ORDER' +export const SET_ACTIVE = 'SET_ACTIVE' +export const SET_ACTIVE_NEXT = 'SET_ACTIVE_NEXT' +export const TOGGLE_HIDE_USER_LIST = 'TOGGLE_HIDE_USER_LIST' diff --git a/src/state/actions/timerActions.ts b/src/state/actions/timerActions.ts index 8e01ebb..0d0df61 100644 --- a/src/state/actions/timerActions.ts +++ b/src/state/actions/timerActions.ts @@ -1,62 +1,22 @@ -// tslint:disable:interface-over-type-literal +import { createStandardAction } from 'typesafe-actions' +import { + ADD_NOTIFICATION, + COUNT_DOWN_FINISHED, + COUNT_DOWN_ONE_SECOND, + SET_TIME, + START_TIMER, + STOP_TIMER, +} from '@state/actions/constants' import { Time } from '../../time' -export const SET_TIME = 'SET_TIME' -export const START_TIMER = 'START_TIMER' -export const STOP_TIMER = 'STOP_TIMER' -export const COUNT_DOWN_ONE_SECOND = 'COUNT_DOWN_ONE_SECOND' -export const COUNT_DOWN_FINISHED = 'COUNT_DOWN_FINISHED' -export const ADD_NOTIFICATION = 'ADD_NOTIFICATION' +export const setTime = createStandardAction(SET_TIME)