Skip to content

Instantly share code, notes, and snippets.

@cpsubrian
Last active September 3, 2015 20:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cpsubrian/b7a85d8bbc71ac67299f to your computer and use it in GitHub Desktop.
Save cpsubrian/b7a85d8bbc71ac67299f to your computer and use it in GitHub Desktop.
A stab at a redux history (undo/redo) 'transducer' utilizing Immutable.js
/**
* History action creators.
*/
import * as types from '../types'
export function resetHistory () {
return {type: types.HISTORY_RESET}
}
export function undo (options = {}) {
return {type: types.HISTORY_UNDO, ...options}
}
export function redo (options = {}) {
return {type: types.HISTORY_REDO, ...options}
}
export function revert (index) {
return {type: types.HISTORY_REVERT, index}
}
import Immutable from 'immutable'
import _ from 'lodash'
import {
HISTORY_RESET,
HISTORY_UNDO,
HISTORY_REDO,
HISTORY_REVERT
} from '../types'
const MAX_SIZE = 200
const initialState = {
cursor: -1,
snapshot: null,
actions: Immutable.List()
}
export default function historyTransducer (reducer) {
const reducers = {
[HISTORY_RESET]: (state) => {
return {...state, history: initialState}
},
[HISTORY_UNDO]: (state, {ignore}) => {
if (state.history) {
let {cursor, actions, snapshot} = state.history
if (cursor >= 0) {
let index = actions.findLastIndex((action, i) => {
if (i <= cursor) {
if (ignore && ignore.indexOf(action.type) >= 0) {
cursor--
}
return (i < cursor)
}
})
if (index < state.history.cursor) {
// Apply actions to snapshot.
let undoState = {...snapshot}
for (let i = 0; i <= index; i++) {
undoState = reducer(undoState, actions.get(i))
}
// Merge and return undo state.
return {...undoState, history: {...state.history, cursor: index}}
}
}
}
return state
},
[HISTORY_REDO]: (state, {ignore}) => {
if (state.history) {
let {cursor, actions} = state.history
if (cursor < actions.size - 1) {
let index = actions.findIndex((action, i) => {
if (i >= cursor) {
if (ignore && ignore.indexOf(action.type) >= 0) {
cursor++
}
return (i > cursor)
}
})
if (index >= 0) {
// Apply actions to cursor state.
let redoState = {...state}
for (let i = cursor + 1; i <= index; i++) {
redoState = reducer(redoState, actions.get(i))
}
// Merge and return redo state.
return {...redoState, history: {...state.history, cursor: index}}
}
}
}
return state
},
[HISTORY_REVERT]: (state, {index}) => {
// @todo Implement this if needed.
return state
},
__SAVE: (state, action) => {
let history = state.history || initialState
let {cursor, snapshot} = history
if (!snapshot) {
snapshot = _.omit(state, 'history')
}
// Update actions.
let actions = history.actions.withMutations((actions) => {
// Clear history after cursor.
for (let i = actions.size - 1; i > cursor; i--) {
actions = actions.pop()
}
// If we're at MAX_SIZE, shift off action and update snapshot.
if (actions.size >= MAX_SIZE) {
let first = actions.first()
snapshot = reducer(snapshot, first)
actions.shift()
}
// Add action to the stack.
actions.push(action)
})
// Merge history into state and return.
return {
...state,
history: {
cursor: actions.size - 1,
snapshot,
actions
}
}
}
}
return function (state, action) {
if (reducers[action.type]) {
return reducers[action.type]({...state, ...reducer(state, action)}, action)
} else {
return {...reducers['__SAVE'](state, action), ...reducer(state, action)}
}
}
}
/**
* Example store initialization.
*/
import {createStore, applyMiddleware, compose} from 'redux'
import reducers from './reducers'
import historyTransducer from './historyTransducer'
const initialState = {}
const store = createStore(historyTransducer(reducers), initialState)
export default store
/**
* Example action type constants.
*/
// Feed Fetching
export const FEED_FETCH = 'FEED_FETCH'
export const FEED_FETCH_FAILED = 'FEED_FETCH_FAILED'
export const FEED_FETCH_SUCCESS = 'FEED_FETCH_SUCCESS'
// History
export const HISTORY_RESET = 'HISTORY_RESET'
export const HISTORY_UNDO = 'HISTORY_UNDO'
export const HISTORY_REDO = 'HISTORY_REDO'
export const HISTORY_REVERT = 'HISTORY_REVERT'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment