Created
August 19, 2017 19:22
-
-
Save sinisterra/99367af9c54be2946c4fcfa3a47d718b to your computer and use it in GitHub Desktop.
Timers using Redux + RxJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { get, omit, includes } from 'lodash' | |
import { combineEpics } from 'redux-observable' | |
import { Observable } from 'rxjs' | |
import update from 'immutability-helper' | |
const MODULE = 'mun-manager/clock' | |
const INTERVAL_DURATION = 1000 | |
export const CLOCK_START = `${MODULE}/CLOCK_START` | |
export const CLOCK_STOP = `${MODULE}/CLOCK_STOP` | |
export const CLOCK_PAUSE = `${MODULE}/CLOCK_PAUSE` | |
export const CLOCK_RESET = `${MODULE}/CLOCK_RESET` | |
export const CLOCK_SET = `${MODULE}/CLOCK_SET` | |
export const CLOCK_TICK = `${MODULE}/CLOCK_TICK` | |
export const CLOCK_REGISTER = `${MODULE}/CLOCK_REGISTER` | |
export const CLOCK_UNREGISTER = `${MODULE}/CLOCK_UNREGISTER` | |
export const types = { | |
CLOCK_START, | |
CLOCK_PAUSE, | |
CLOCK_STOP, | |
CLOCK_SET, | |
CLOCK_RESET, | |
CLOCK_TICK, | |
CLOCK_REGISTER, | |
CLOCK_UNREGISTER | |
} | |
const Clock = (duration = 60 * 10) => ({ | |
duration, | |
count: 0, | |
status: 'PAUSED' | |
}) | |
const initialState = { | |
sl: Clock(90), | |
mc: Clock(10 * 60), | |
session: Clock(90 * 60), | |
} | |
export const clockStart = clock => ({ | |
type: CLOCK_START, | |
payload: { | |
clock | |
} | |
}) | |
export const clockPause = clock => ({ | |
type: CLOCK_PAUSE, | |
payload: { | |
clock | |
} | |
}) | |
export const clockStop = clock => ({ | |
type: CLOCK_STOP, | |
payload: { | |
clock | |
} | |
}) | |
export const clockReset = clock => ({ | |
type: CLOCK_RESET, | |
payload: { | |
clock | |
} | |
}) | |
export const clockTick = clock => ({ | |
type: CLOCK_TICK, | |
payload: { | |
clock | |
} | |
}) | |
export const clockSet = (clock, duration) => ({ | |
type: CLOCK_SET, | |
payload: { | |
clock, | |
duration | |
} | |
}) | |
export const clockRegister = clock => ({ | |
type: CLOCK_REGISTER, | |
payload: { | |
clock | |
} | |
}) | |
export const clockUnregister = clock => ({ | |
type: CLOCK_UNREGISTER, | |
payload: { | |
clock | |
} | |
}) | |
export const selectClock = (state, clock) => get(state, `clocks.${clock}`) | |
const actionsThatStopClock = [CLOCK_REGISTER, CLOCK_UNREGISTER] | |
export const clockStopEpic = action$ => action$.filter(action => { | |
return includes(actionsThatStopClock, action.type) | |
}) | |
.map(action => clockStop(action.payload.clock)) | |
export const clockStartEpic = (action$, store) => action$.ofType(CLOCK_START) | |
.flatMap(action => { | |
const clockId = action.payload.clock | |
const thisClock = selectClock(store.getState(), clockId) | |
// return an empty observable if this clock is already running. Avoid multiple interval creation | |
return thisClock.status === 'RUNNING' ? Observable.of() : Observable.of(action) | |
}) | |
.flatMap(action => { | |
const clockId = action.payload.clock | |
return Observable.timer(0, INTERVAL_DURATION) | |
.map(count => { | |
const clock = selectClock(store.getState(), clockId) | |
if (clock === undefined) { | |
return clockStop(clockId) | |
} | |
return clockTick(clockId) | |
// uncomment this to stop the clock on the hour. Don't count backwards | |
/* if (clock.count < clock.duration && clock.count >= 0 && clock.status !== 'FINISHED') { | |
return clockTick(clockId) | |
}*/ | |
// return clockStop(clockId) | |
}) | |
.takeUntil(action$ | |
.filter(action => action.payload.clock === clockId) | |
.filter(action => action.type === CLOCK_PAUSE || action.type === CLOCK_STOP || action.type === CLOCK_RESET) | |
) | |
}) | |
export const reducer = (state = initialState, action) => { | |
switch (action.type) { | |
case CLOCK_STOP: | |
return { | |
...state, | |
[action.payload.clock]: update(state[action.payload.clock], { | |
$merge: { | |
status: 'FINISHED' | |
} | |
}) | |
} | |
case CLOCK_TICK: | |
return { | |
...state, | |
[action.payload.clock]: update(state[action.payload.clock], { | |
$merge: { | |
status: 'RUNNING', | |
count: state[action.payload.clock].count + 1, | |
} | |
}) | |
} | |
case CLOCK_RESET: | |
return { | |
...state, | |
[action.payload.clock]: update(state[action.payload.clock], { | |
$merge: { | |
count: 0, | |
status: 'PAUSED' | |
} | |
}) | |
} | |
case CLOCK_SET: | |
return { | |
...state, | |
[action.payload.clock]: update(state[action.payload.clock], { | |
$merge: { | |
duration: action.payload.duration, | |
status: 'PAUSED' | |
} | |
}) | |
} | |
case CLOCK_PAUSE: | |
return { | |
...state, | |
[action.payload.clock]: update(state[action.payload.clock], { | |
$merge: { | |
status: state[action.payload.clock].status !== 'FINISHED' ? 'PAUSED' : 'FINISHED' | |
} | |
}) | |
} | |
case CLOCK_REGISTER: | |
return update(state, { | |
$merge: { | |
[action.payload.clock]: Clock() | |
} | |
}) | |
case CLOCK_UNREGISTER: | |
return omit(state, action.payload.clock) | |
default: | |
return state | |
} | |
} | |
export const epic = combineEpics(clockStartEpic, clockStopEpic) | |
export default reducer | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment