1 | import React, { Component } from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import JSONTree from 'react-json-tree';
|
4 | import LogMonitorEntryAction from './LogMonitorEntryAction';
|
5 | import shouldPureComponentUpdate from 'react-pure-render/function';
|
6 |
|
7 | const styles = {
|
8 | entry: {
|
9 | display: 'block',
|
10 | WebkitUserSelect: 'none'
|
11 | },
|
12 |
|
13 | root: {
|
14 | marginLeft: 0
|
15 | }
|
16 | };
|
17 |
|
18 | const getDeepItem = (data, path) =>
|
19 | path.reduce((obj, key) => obj && obj[key], data);
|
20 | const dataIsEqual = (data, previousData, keyPath) => {
|
21 | const path = [...keyPath].reverse().slice(1);
|
22 |
|
23 | return getDeepItem(data, path) === getDeepItem(previousData, path);
|
24 | };
|
25 |
|
26 | export default class LogMonitorEntry extends Component {
|
27 | static propTypes = {
|
28 | state: PropTypes.object.isRequired,
|
29 | action: PropTypes.object.isRequired,
|
30 | actionId: PropTypes.number.isRequired,
|
31 | select: PropTypes.func.isRequired,
|
32 | inFuture: PropTypes.bool,
|
33 | error: PropTypes.string,
|
34 | onActionClick: PropTypes.func.isRequired,
|
35 | onActionShiftClick: PropTypes.func.isRequired,
|
36 | collapsed: PropTypes.bool,
|
37 | selected: PropTypes.bool,
|
38 | expandActionRoot: PropTypes.bool,
|
39 | expandStateRoot: PropTypes.bool
|
40 | };
|
41 |
|
42 | shouldComponentUpdate = shouldPureComponentUpdate;
|
43 |
|
44 | constructor(props) {
|
45 | super(props);
|
46 | this.handleActionClick = this.handleActionClick.bind(this);
|
47 | this.shouldExpandNode = this.shouldExpandNode.bind(this);
|
48 | }
|
49 |
|
50 | printState(state, error) {
|
51 | let errorText = error;
|
52 | if (!errorText) {
|
53 | try {
|
54 | const data = this.props.select(state);
|
55 | let theme;
|
56 |
|
57 | if (this.props.markStateDiff) {
|
58 | const previousData =
|
59 | typeof this.props.previousState !== 'undefined'
|
60 | ? this.props.select(this.props.previousState)
|
61 | : undefined;
|
62 | const getValueStyle = ({ style }, nodeType, keyPath) => ({
|
63 | style: {
|
64 | ...style,
|
65 | backgroundColor: dataIsEqual(data, previousData, keyPath)
|
66 | ? 'transparent'
|
67 | : this.props.theme.base01
|
68 | }
|
69 | });
|
70 | const getNestedNodeStyle = ({ style }, keyPath) => ({
|
71 | style: {
|
72 | ...style,
|
73 | ...(keyPath.length > 1 ? {} : styles.root)
|
74 | }
|
75 | });
|
76 | theme = {
|
77 | extend: this.props.theme,
|
78 | tree: styles.tree,
|
79 | value: getValueStyle,
|
80 | nestedNode: getNestedNodeStyle
|
81 | };
|
82 | } else {
|
83 | theme = this.props.theme;
|
84 | }
|
85 |
|
86 | return (
|
87 | <JSONTree
|
88 | theme={theme}
|
89 | data={data}
|
90 | invertTheme={false}
|
91 | keyPath={['state']}
|
92 | shouldExpandNode={this.shouldExpandNode}
|
93 | />
|
94 | );
|
95 | } catch (err) {
|
96 | errorText = 'Error selecting state.';
|
97 | }
|
98 | }
|
99 |
|
100 | return (
|
101 | <div
|
102 | style={{
|
103 | color: this.props.theme.base08,
|
104 | paddingTop: 20,
|
105 | paddingLeft: 30,
|
106 | paddingRight: 30,
|
107 | paddingBottom: 35
|
108 | }}
|
109 | >
|
110 | {errorText}
|
111 | </div>
|
112 | );
|
113 | }
|
114 |
|
115 | handleActionClick(e) {
|
116 | const { actionId, onActionClick, onActionShiftClick } = this.props;
|
117 | if (actionId > 0) {
|
118 | if (e.shiftKey) {
|
119 | onActionShiftClick(actionId);
|
120 | } else {
|
121 | onActionClick(actionId);
|
122 | }
|
123 | }
|
124 | }
|
125 |
|
126 | shouldExpandNode(keyName, data, level) {
|
127 | return this.props.expandStateRoot && level === 0;
|
128 | }
|
129 |
|
130 | render() {
|
131 | const {
|
132 | actionId,
|
133 | error,
|
134 | action,
|
135 | state,
|
136 | collapsed,
|
137 | selected,
|
138 | inFuture
|
139 | } = this.props;
|
140 | const styleEntry = {
|
141 | opacity: collapsed ? 0.5 : 1,
|
142 | cursor: actionId > 0 ? 'pointer' : 'default'
|
143 | };
|
144 |
|
145 | return (
|
146 | <div
|
147 | style={{
|
148 | opacity: selected ? 0.4 : inFuture ? 0.6 : 1,
|
149 | textDecoration: collapsed ? 'line-through' : 'none',
|
150 | color: this.props.theme.base06
|
151 | }}
|
152 | >
|
153 | <LogMonitorEntryAction
|
154 | theme={this.props.theme}
|
155 | collapsed={collapsed}
|
156 | action={action}
|
157 | expandActionRoot={this.props.expandActionRoot}
|
158 | onClick={this.handleActionClick}
|
159 | style={{ ...styles.entry, ...styleEntry }}
|
160 | />
|
161 | {!collapsed && (
|
162 | <div style={{ paddingLeft: 16 }}>{this.printState(state, error)}</div>
|
163 | )}
|
164 | </div>
|
165 | );
|
166 | }
|
167 | }
|