From eee6f0f5e08732356d5c77784420211cc2bf3fe7 Mon Sep 17 00:00:00 2001 From: Steve Kellock Date: Sat, 20 Aug 2016 13:31:03 -0400 Subject: [PATCH] Adds subscription diffs. (#143) --- .../App/Commands/StateValuesChangeCommand.js | 29 +++++++--- .../reactotron-app/App/Lib/ShallowDiff.js | 54 +++++++++++++++++++ .../reactotron-app/App/Stores/SessionStore.js | 5 ++ 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 packages/reactotron-app/App/Lib/ShallowDiff.js diff --git a/packages/reactotron-app/App/Commands/StateValuesChangeCommand.js b/packages/reactotron-app/App/Commands/StateValuesChangeCommand.js index 1dbc357c9..3c9dd9200 100644 --- a/packages/reactotron-app/App/Commands/StateValuesChangeCommand.js +++ b/packages/reactotron-app/App/Commands/StateValuesChangeCommand.js @@ -4,7 +4,8 @@ import ObjectTree from '../Shared/ObjectTree' import Colors from '../Theme/Colors' import isShallow from '../Lib/IsShallow' import makeTable from '../Shared/MakeTable' -import { map, fromPairs } from 'ramda' +import { keys, concat, join } from 'ramda' +import { mapKeys, isNilOrEmpty } from 'ramdasauce' const COMMAND_TITLE = 'SUBSCRIPTIONS' @@ -32,15 +33,31 @@ class StateValuesChangeCommand extends Component { render () { const { command } = this.props - const changes = map(change => ([change.path, change.value]), this.props.command.payload.changes) - const changeObject = fromPairs(changes) - const preview = null // `${changes.length} change${changes.length !== 1 && 's'}` + const { payload } = command + const phrase = [] + let { changed, added, removed } = payload + const hasAdded = !isNilOrEmpty(added) + const hasRemoved = !isNilOrEmpty(removed) + const hasChanged = !isNilOrEmpty(changed) + if (hasChanged) { + phrase.push(`${keys(changed).length} changed`) + } + if (hasAdded) { + added = mapKeys(concat('+ '), added) + phrase.push(`${keys(added).length} added`) + } + if (hasRemoved) { + removed = mapKeys(concat('- '), removed) + phrase.push(`${keys(removed).length} removed`) + } + const preview = join(' ', phrase) return (
-
{name}
- {this.renderContent(changeObject)} + {hasChanged && this.renderContent(changed)} + {hasAdded && this.renderContent(added)} + {hasRemoved && this.renderContent(removed)}
) diff --git a/packages/reactotron-app/App/Lib/ShallowDiff.js b/packages/reactotron-app/App/Lib/ShallowDiff.js new file mode 100644 index 000000000..0aa4dd953 --- /dev/null +++ b/packages/reactotron-app/App/Lib/ShallowDiff.js @@ -0,0 +1,54 @@ +// https://github.com/ramda/ramda/wiki/Cookbook#diffobjs---diffing-objects-similar-to-guavas-mapsdifference +import { + curry, pipe, useWith, __, map, + toPairs, last, fromPairs, + groupBy, mergeWith, always, has, both, + objOf, merge, prop, apply, ifElse, + cond, values, equals, evolve +} from 'ramda' // boom + +const groupObjBy = curry( + pipe( + // Call groupBy with the object as pairs, passing only the value to the key function + useWith(groupBy, [useWith(__, [last]), toPairs]), + map(fromPairs) + ) +) + +const LEFT_VALUE = 'leftValue' +const RIGHT_VALUE = 'rightValue' +const COMMON = 'common' +const DIFFERENCE = 'difference' +const ONLY_ON_LEFT = 'onlyOnLeft' +const ONLY_ON_RIGHT = 'onlyOnRight' + +const diffObjs = pipe( + useWith( + mergeWith(merge), + [map(objOf(LEFT_VALUE)), map(objOf(RIGHT_VALUE))] + ), + + groupObjBy(cond([ + [ + both(has(LEFT_VALUE), has(RIGHT_VALUE)), + pipe( + values, + ifElse( + apply(equals), + always(COMMON), + always(DIFFERENCE) + ) + ) + ], + [has(LEFT_VALUE), always(ONLY_ON_LEFT)], + [has(RIGHT_VALUE), always(ONLY_ON_RIGHT)] + ])), + + evolve({ + [COMMON]: map(prop(LEFT_VALUE)), + [ONLY_ON_LEFT]: map(prop(LEFT_VALUE)), + [ONLY_ON_RIGHT]: map(prop(RIGHT_VALUE)) + }) +) + +export default diffObjs diff --git a/packages/reactotron-app/App/Stores/SessionStore.js b/packages/reactotron-app/App/Stores/SessionStore.js index ccb1fa7c9..054490cd0 100644 --- a/packages/reactotron-app/App/Stores/SessionStore.js +++ b/packages/reactotron-app/App/Stores/SessionStore.js @@ -3,6 +3,7 @@ import { createServer } from 'reactotron-core-server' import { computed, reaction } from 'mobx' import { last, isNil, reject, equals, reverse, pipe, propEq, map, fromPairs } from 'ramda' import { dotPath } from 'ramdasauce' +import shallowDiff from '../Lib/ShallowDiff' const isSubscription = propEq('type', 'state.values.change') const isSubscriptionCommandWithEmptyChanges = command => isSubscription(command) && dotPath('payload.changes.length', command) === 0 @@ -21,6 +22,10 @@ class Session { // 🚨🚨🚨 side effect alert 🚨🚨🚨 if (isNew) { + const diff = shallowDiff(this.subscriptions, newSubscriptions) + command.payload.changed = map(v => v.rightValue, diff.difference || []) + command.payload.added = diff.onlyOnRight || [] + command.payload.removed = diff.onlyOnLeft || [] this.subscriptions = newSubscriptions } // 🚨🚨🚨 side effect alert 🚨🚨🚨