Skip to content

Instantly share code, notes, and snippets.

@sinisterra
Created August 19, 2017 19:22
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sinisterra/99367af9c54be2946c4fcfa3a47d718b to your computer and use it in GitHub Desktop.
Save sinisterra/99367af9c54be2946c4fcfa3a47d718b to your computer and use it in GitHub Desktop.
Timers using Redux + RxJS
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