diff --git a/src/components/Dock/Dock.js b/src/components/Dock/Dock.js
index 5d96633..1106b74 100644
--- a/src/components/Dock/Dock.js
+++ b/src/components/Dock/Dock.js
@@ -16,7 +16,7 @@ class Dock extends React.Component {
node = null
state = {
- visible: false,
+ visible: true,
width: 500,
isResizing: false
}
diff --git a/src/components/Effect/Effect.js b/src/components/Effect/Effect.js
index a6bb893..6afa5cf 100644
--- a/src/components/Effect/Effect.js
+++ b/src/components/Effect/Effect.js
@@ -18,21 +18,31 @@ class Effect extends React.Component {
effectId = this.props.effect.effectId
+ highlightFilter = (text) => {
+ const highlight = this.props.filter
+ const parts = text.split(new RegExp(`(${highlight})`, 'gi'))
+
+ return {parts.map((part, i) =>
+
+ {part}
+ )
+ }
+ }
+
renderResult(status, result, error, winner) {
return
}
render() {
- const {effect} = this.props
+ const {effect, filter} = this.props
const { status, result, error, winner } = effect
-
let nodes = []
let data
if(effect.root) {
nodes = nodes.concat(
- renderFuncCall(effect.effect.saga, effect.effect.args),
+ renderFuncCall(effect.effect.saga, effect.effect.args, this.highlightFilter),
this.renderResult(status, result, error)
)
}
@@ -40,7 +50,11 @@ class Effect extends React.Component {
else if((data = asEffect.take(effect.effect))) {
nodes = nodes.concat(
renderEffectType('take'),
- ,
+ ,
this.renderResult(status, result, error, winner)
)
}
@@ -48,14 +62,19 @@ class Effect extends React.Component {
else if((data = asEffect.put(effect.effect))) {
nodes = nodes.concat(
renderEffectType('put'),
-
+
)
}
else if((data = asEffect.call(effect.effect))) {
nodes = nodes.concat(
renderEffectType('call'),
- renderFuncCall(data.fn, data.args),
+ renderFuncCall(data.fn, data.args, this.highlightFilter),
this.renderResult(status, result, error, winner)
)
}
@@ -71,7 +90,7 @@ class Effect extends React.Component {
else if((data = asEffect.fork(effect.effect))) {
nodes = nodes.concat(
renderEffectType('fork'),
- renderFuncCall(data.fn, data.args),
+ renderFuncCall(data.fn, data.args, this.highlightFilter),
this.renderResult(status, result, error, winner)
)
}
@@ -79,7 +98,12 @@ class Effect extends React.Component {
else if((data = asEffect.join(effect.effect))) {
nodes = nodes.concat(
renderEffectType('join'),
- ,
+ ,
this.renderResult(status, result, error, winner)
)
}
@@ -87,7 +111,12 @@ class Effect extends React.Component {
else if((data = asEffect.cancel(effect.effect))) {
nodes = nodes.concat(
renderEffectType('cancel'),
- ,
+ ,
)
}
@@ -108,7 +137,7 @@ class Effect extends React.Component {
else if((data = asEffect.select(effect.effect))) {
nodes = nodes.concat(
renderEffectType('select'),
- renderFuncCall(data.selector, data.args),
+ renderFuncCall(data.selector, data.args, this.highlightFilter),
this.renderResult(status, result, error, winner)
)
}
@@ -116,7 +145,11 @@ class Effect extends React.Component {
else if((data = asEffect.actionChannel(effect.effect))) {
nodes = nodes.concat(
renderEffectType('actionChannel'),
- ,
+ ,
this.renderResult(status, result, error, winner)
)
}
@@ -172,13 +205,13 @@ function renderEffectType(type) {
)
}
-function renderFuncCall(fn, args) {
+function renderFuncCall(fn, args, highlighter = val => val) {
if(!args.length) {
- return {fn.name}()
+ return {highlighter(fn.name)}()
}
return [
- {fn.name}(,
+ {highlighter(fn.name)}(,
...renderFuncArgs(args),
)
]
@@ -197,6 +230,7 @@ function renderFuncArgs(args) {
Effect.propTypes = {
effect: PropTypes.object.isRequired,
+ filter: PropTypes.string.isRequired,
}
export default Effect
diff --git a/src/components/JSValue/JSValue.js b/src/components/JSValue/JSValue.js
index a5ea889..18328f9 100644
--- a/src/components/JSValue/JSValue.js
+++ b/src/components/JSValue/JSValue.js
@@ -12,7 +12,7 @@ import {
const vnull =
const vfuncKeyword = function
-function renderValue(value, isIdentifier, label, onlyPrimitive) {
+function renderValue(value, isIdentifier, label, onlyPrimitive, highlighter = val => val) {
if(value === null || value === undefined) {
return vnull
@@ -25,9 +25,9 @@ function renderValue(value, isIdentifier, label, onlyPrimitive) {
const type = typeof value
if(type === 'string') {
if(isIdentifier) {
- return {value}
+ return {highlighter(value)}
} else {
- return '{value}'
+ return '{highlighter(value)}'
}
}
if(
@@ -35,14 +35,14 @@ function renderValue(value, isIdentifier, label, onlyPrimitive) {
type === 'number' ||
type === 'boolean'
) {
- return {String(value)}
+ return {highlighter(String(value))}
}
else if(type === 'function') {
return (
{vfuncKeyword}
- {value.name}()
+ {highlighter(value.name)}()
)
}
@@ -52,7 +52,7 @@ function renderValue(value, isIdentifier, label, onlyPrimitive) {
{label}
}
return (
-
+
)
}
}
@@ -65,17 +65,18 @@ function getObjectSummary(obj) {
)
}
-function JSValue({value, isIdentifier, label}) {
- return renderValue(value, isIdentifier, label, false)
+function JSValue({value, isIdentifier, label, highlighter = val => val}) {
+ return renderValue(value, isIdentifier, label, false, highlighter)
}
JSValue.propTypes = {
value: PropTypes.any,
isIdentifier: PropTypes.bool,
label: PropTypes.any,
+ highlighter: PropTypes.func,
}
-export function JSObject({data, renderLabel, preview, ignoreLabelClick}) {
+export function JSObject({data, renderLabel, preview, ignoreLabelClick, highlighter = val => val}) {
const keys = Object.keys(data)
if(!keys.length) {
return renderLabel ? renderLabel() : '{}'
@@ -137,6 +138,7 @@ JSObject.propTypes = {
renderLabel: PropTypes.func,
preview: PropTypes.any,
ignoreLabelClick: PropTypes.bool,
+ highlighter: PropTypes.func,
}
export default JSValue
diff --git a/src/components/SagaValue/SagaValue.js b/src/components/SagaValue/SagaValue.js
index a56de61..a5d1de8 100644
--- a/src/components/SagaValue/SagaValue.js
+++ b/src/components/SagaValue/SagaValue.js
@@ -3,7 +3,7 @@ import { is, CHANNEL_END } from 'redux-saga/utils'
import JSValue from '../JSValue'
import SagaRef from '../../containers/SagaRef'
-export default function SagaValue({value, label, isIdentifier}) {
+export default function SagaValue({value, label, isIdentifier, highlighter}) {
if(is.channel(value)) {
return {label || 'Channel'}
}
@@ -11,6 +11,6 @@ export default function SagaValue({value, label, isIdentifier}) {
return
}
else {
- return
+ return
}
}
diff --git a/src/containers/EffectEntry/EffectEntry.js b/src/containers/EffectEntry/EffectEntry.js
index 14c9126..589ef14 100644
--- a/src/containers/EffectEntry/EffectEntry.js
+++ b/src/containers/EffectEntry/EffectEntry.js
@@ -48,7 +48,7 @@ class EffectEntry extends React.Component {
onUnpin = () => this.props.onUnpin(-1)
render() {
- const {effect, collapsed, pinned, hasChildren} = this.props
+ const {effect, collapsed, pinned, hasChildren, passFilter, filter} = this.props
let pinNode
if(!effect.root) {
@@ -60,6 +60,7 @@ class EffectEntry extends React.Component {
}
return (
+ passFilter ?
@@ -70,7 +71,7 @@ class EffectEntry extends React.Component {
/>
|
-
+
|
{
@@ -88,6 +89,8 @@ class EffectEntry extends React.Component {
)
}
+ :
+ null
)
}
}
@@ -99,6 +102,8 @@ EffectEntry.propTypes = {
collapsed: PropTypes.bool.isRequired,
onCollapse: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
+ filter: PropTypes.string.isRequired,
+ passFilter: PropTypes.bool.isRequired,
// injected by Redux store
effect: PropTypes.object.isRequired,
hasChildren: PropTypes.bool.isRequired,
@@ -109,7 +114,7 @@ export default connect(
const effect = state.effectsById[effectId]
return {
effect,
- hasChildren: state.effectsByParentId[effectId]
+ hasChildren: !!state.effectsByParentId[effectId]
}
}
)(EffectEntry)
diff --git a/src/containers/EffectList/EffectList.js b/src/containers/EffectList/EffectList.js
index 4628072..7904705 100644
--- a/src/containers/EffectList/EffectList.js
+++ b/src/containers/EffectList/EffectList.js
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react';
import styled, { css } from 'styled-components'
import { connect } from 'react-redux'
+import { asEffect } from 'redux-saga/utils'
import { matchCurrentAction } from '../../store/selectors'
import {
KEY_ARROW_DOWN,
@@ -25,14 +26,51 @@ const cssMatchAction = css`
margin-top: -1px;
`
+const EFFECT_TYPES = ['take', 'put', 'fork', 'call', 'cps', 'join', 'cancel']
+
class EffectList extends React.Component {
state = {
collapsedEffects: {}
}
+ componentDidMount() {
+ this.props.setFilterOptions(EFFECT_TYPES, { allCaps: true })
+ }
+
isCollapsed = effectId => {
- return this.state.collapsedEffects[effectId]
+ return !!this.state.collapsedEffects[effectId]
+ }
+
+ passFilter = effectId => {
+ const { filter } = this.props
+
+ if (!filter.word && !filter.type) return true
+
+ let data = {}, description
+ const effect = this.props.effectsById[effectId]
+
+ if (effect.root) {
+ data = { type: 'root', name: effect.effect.saga.name }
+ }
+
+ else { // Currently only filters those with string data.name
+ EFFECT_TYPES.forEach(type => {
+ if (description = asEffect[type](effect.effect)) {
+ data = {
+ type,
+ name: description.pattern
+ || description.channel
+ || description.action
+ || (description.fn && description.fn.name)
+ }
+ }
+ })
+ }
+
+ return typeof data.name === 'string'
+ && data.name.toLowerCase().includes(filter.word.toLowerCase())
+ && (!filter.type || data.type === filter.type)
}
collapseEffect = (effectId, collapsed) => {
@@ -121,6 +159,8 @@ class EffectList extends React.Component {
selected={this.props.selectedEffectId === effectId}
pinned={this.props.pinnedEffectId === effectId}
collapsed={this.isCollapsed(effectId)}
+ filter={this.props.filter.word}
+ passFilter={this.passFilter(effectId)}
onCollapse={this.collapseEffect}
onPin={this.props.onPin}
onUnpin={this.props.onUnpin}
@@ -154,6 +194,8 @@ EffectList.propTypes = {
selectedEffectId: PropTypes.number,
onSelectionChange: PropTypes.func.isRequired,
rootEffectIds: PropTypes.array.isRequired,
+ filter: PropTypes.object.isRequired,
+ setFilterOptions: PropTypes.func.isRequired,
// Injected by redux
effectsById: PropTypes.object.isRequired,
effectsByParentId: PropTypes.object.isRequired,
diff --git a/src/containers/EffectView/EffectView.js b/src/containers/EffectView/EffectView.js
index d9ab52b..b4ac40a 100644
--- a/src/containers/EffectView/EffectView.js
+++ b/src/containers/EffectView/EffectView.js
@@ -64,7 +64,8 @@ class EffectView extends React.Component {
render() {
- const rootEffectIds = this.props.rootEffectIds
+ const {rootEffectIds, filter, setFilterOptions} = this.props
+
const selectedEffectId = this.state.selectedEffectId
const pinnedEffectId = this.state.pinnedEffectId
@@ -78,6 +79,8 @@ class EffectView extends React.Component {
pinnedEffectId={pinnedEffectId}
onPin={this.handlePin}
onUnpin={this.handleUnpin}
+ filter={filter}
+ setFilterOptions={setFilterOptions}
/>
@@ -94,6 +97,8 @@ class EffectView extends React.Component {
EffectView.propTypes = {
rootEffectIds: PropTypes.array.isRequired,
+ filter: PropTypes.object.isRequired,
+ // setFilterOptions: PropTypes.func.isRequired,
// Inject by Redux
effectsById: PropTypes.object.isRequired
}
diff --git a/src/containers/SagaMonitorView/SagaMonitorView.js b/src/containers/SagaMonitorView/SagaMonitorView.js
index 6a36f91..ca4ba16 100644
--- a/src/containers/SagaMonitorView/SagaMonitorView.js
+++ b/src/containers/SagaMonitorView/SagaMonitorView.js
@@ -9,7 +9,10 @@ import {
SagaMonitorContainer,
SagaMonitorHeader,
SagaMonitorOption,
- SagaMonitorBody
+ SagaMonitorBody,
+ FilterEffect,
+ FilterDropdown,
+ FilterOption
} from './styles'
const EFFECT_VIEW = 'Effects'
@@ -22,7 +25,9 @@ class SagaMonitorView extends React.Component {
state = {
currentView: EFFECT_VIEW,
- currentViewIndex: 0
+ currentViewIndex: 0,
+ filter: { word: '', type: undefined },
+ filterOptions: []
}
viewHandlers = {
@@ -30,10 +35,44 @@ class SagaMonitorView extends React.Component {
[ACTION_VIEW]: () => this.setState({ currentView: ACTION_VIEW, currentViewIndex: 1 })
}
+ updateFilter = () => this.setState(({ filter }) => (
+ { filter: { word: this.filterWord.value, type: this.filterType.value } }
+ ))
+
+ setFilterOptions = (items, { allCaps }) => {
+ const options = items.map(item => {
+ let label, value
+
+ if(typeof item === 'object') {
+ label = item.option || item.value
+ value = item.value
+ }
+ else if(typeof item === 'string') {
+ label = item
+ value = item
+ }
+
+ if (allCaps) label = label.toUpperCase()
+
+ return { label, value }
+
+ })
+
+ this.setState({ filterOptions: options })
+ }
+
+ renderFilterOptions = () => this.state.filterOptions.map(option => {
+ return {option.label}
+ })
+
renderCurrentView() {
switch (this.state.currentView) {
case EFFECT_VIEW:
- return
+ return
case ACTION_VIEW:
return
default:
@@ -62,6 +101,18 @@ class SagaMonitorView extends React.Component {
{this.renderViewOption(EFFECT_VIEW)}
{this.renderViewOption(ACTION_VIEW)}
+ this.filterWord = filter}
+ placeholder="filter..."
+ />
+ this.filterType = filter}
+ >
+ none
+ {this.renderFilterOptions()}
+
diff --git a/src/containers/SagaMonitorView/styles.js b/src/containers/SagaMonitorView/styles.js
index c391c19..a50b9d1 100644
--- a/src/containers/SagaMonitorView/styles.js
+++ b/src/containers/SagaMonitorView/styles.js
@@ -49,3 +49,20 @@ export const SagaMonitorBody = styled.section`
bottom: 0;
width: 100%;
`
+
+export const FilterEffect = styled.input`
+ width: 100px;
+ margin: 4px 10px;
+
+ &::placeholder {
+ padding-left: 4px;
+ }
+`
+
+export const FilterDropdown = styled.select`
+ width: 60px;
+`
+
+export const FilterOption = styled.option`
+ width: 30px;
+`