Flow Coverage Report - src/components/SecondaryPanes/Expressions.js

FilenamePercentTotalCoveredUncovered
src/components/SecondaryPanes/Expressions.js 92 % 343 316 27
x
 
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
4
5
// @flow
6
import React, { Component } from "react";
7
import { connect } from "react-redux";
8
import classnames from "classnames";
9
import { ObjectInspector } from "devtools-reps";
10
11
import actions from "../../actions";
12
import { getExpressions, getExpressionError } from "../../selectors";
13
import { getValue } from "../../utils/expressions";
14
import { createObjectClient } from "../../client/firefox";
15
16
import CloseButton from "../shared/Button/Close";
17
18
import type { List } from "immutable";
19
import type { Expression } from "../../types";
20
21
import "./Expressions.css";
22
23
type State = {
24
  editing: boolean,
25
  editIndex: number,
26
  inputValue: string
27
};
28
29
type Props = {
30
  expressions: List<Expression>,
31
  expressionError: boolean,
32
  addExpression: (input: string) => void,
33
  clearExpressionError: () => void,
34
  evaluateExpressions: () => void,
35
  updateExpression: (input: string, expression: Expression) => void,
36
  deleteExpression: (expression: Expression) => void,
37
  openLink: (url: string) => void
38
};
39
40
class Expressions extends Component<Props, State> {
41
  _input: ?HTMLInputElement;
42
  renderExpression: (
43
    expression: Expression,
44
    index: number
45
  ) => React$Element<"li">;
46
47
  constructor(props: Props) {
48
    super(props);
49
    this.state = { editing: false, editIndex: -1, inputValue: "" };
50
  }
51
52
  componentDidMount() {
53
1x
    const { expressions, evaluateExpressions } = this.props;
54
2x
    if (expressions.size > 0) {
55
      evaluateExpressions();
56
    }
57
  }
58
59
  clear = () => {
60
    this.setState(() => {
61
      this.props.clearExpressionError();
62
      return { editing: false, editIndex: -1, inputValue: "" };
63
    });
64
  };
65
66
  componentWillReceiveProps(nextProps) {
67
    if (this.state.editing && !nextProps.expressionError) {
68
      this.clear();
69
    }
70
  }
71
72
  shouldComponentUpdate(nextProps, nextState) {
73
    const { editing, inputValue } = this.state;
74
1x
    const { expressions, expressionError } = this.props;
75
    return (
76
2x
      expressions !== nextProps.expressions ||
77
      expressionError !== nextProps.expressionError ||
78
      editing !== nextState.editing ||
79
      inputValue !== nextState.inputValue
80
    );
81
  }
82
83
  componentDidUpdate(prevProps, prevState) {
84
    if (this._input && !prevState.editing) {
85
      const input = this._input;
86
      input.setSelectionRange(0, input.value.length);
87
      input.focus();
88
    }
89
  }
90
91
  editExpression(expression: Expression, index: number) {
92
    this.setState({
93
      inputValue: expression.input,
94
      editing: true,
95
      editIndex: index
96
    });
97
  }
98
99
  deleteExpression(
100
    e: SyntheticMouseEvent<HTMLDivElement>,
101
    expression: Expression
102
  ) {
103
    e.stopPropagation();
104
    const { deleteExpression } = this.props;
105
    deleteExpression(expression);
106
  }
107
108
  handleChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
109
    this.setState({ inputValue: e.target.value });
110
  };
111
112
  handleKeyDown = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
113
    if (e.key === "Escape") {
114
      this.clear();
115
    }
116
  };
117
118
  handleExistingSubmit = async (
119
    e: SyntheticEvent<HTMLFormElement>,
120
    expression: Expression
121
  ) => {
122
    e.preventDefault();
123
    e.stopPropagation();
124
125
    this.props.updateExpression(this.state.inputValue, expression);
126
  };
127
128
  handleNewSubmit = async (e: SyntheticEvent<HTMLFormElement>) => {
129
    const { inputValue } = this.state;
130
    e.preventDefault();
131
    e.stopPropagation();
132
133
    this.props.clearExpressionError();
134
    await this.props.addExpression(this.state.inputValue);
135
    this.setState({
136
      editing: false,
137
      editIndex: -1,
138
      inputValue: this.props.expressionError ? inputValue : ""
139
    });
140
  };
141
142
  renderExpression = (expression: Expression, index: number) => {
143
    const { expressionError, openLink } = this.props;
144
    const { editing, editIndex } = this.state;
145
    const { input, updating } = expression;
146
    const isEditingExpr = editing && editIndex === index;
147
    if (isEditingExpr || (isEditingExpr && expressionError)) {
148
      return this.renderExpressionEditInput(expression);
149
    }
150
151
    if (updating) {
152
      return;
153
    }
154
155
    const { value } = getValue(expression);
156
157
    const root = {
158
      name: expression.input,
159
      path: input,
160
      contents: { value }
161
    };
162
163
    return (
164
      <li
165
        className="expression-container"
166
        key={input}
167
2x
        onDoubleClick={(items, options) =>
168
          this.editExpression(expression, index)
169
        }
170
      >
171
        <div className="expression-content">
172
          <ObjectInspector
173
            roots={[root]}
174
            autoExpandDepth={0}
175
            disableWrap={true}
176
            disabledFocus={true}
177
            openLink={openLink}
178
3x
            createObjectClient={grip => createObjectClient(grip)}
179
          />
180
          <div className="expression-container__close-btn">
181
            <CloseButton
182
2x
              handleClick={e => this.deleteExpression(e, expression)}
183
            />
184
          </div>
185
        </div>
186
      </li>
187
    );
188
  };
189
190
  renderNewExpressionInput() {
191
    const { expressionError } = this.props;
192
    const { editing, inputValue } = this.state;
193
    const error = editing === false && expressionError === true;
194
    const placeholder: string = error
195
      ? L10N.getStr("expressions.errorMsg")
196
      : L10N.getStr("expressions.placeholder");
197
    return (
198
      <li className="expression-input-container">
199
        <form className="expression-input-form" onSubmit={this.handleNewSubmit}>
200
          <input
201
            className={classnames("input-expression", { error })}
202
            type="text"
203
            placeholder={placeholder}
204
            onChange={this.handleChange}
205
            onBlur={this.clear}
206
            onKeyDown={this.handleKeyDown}
207
            value={!editing ? inputValue : ""}
208
          />
209
          <input type="submit" style={{ display: "none" }} />
210
        </form>
211
      </li>
212
    );
213
  }
214
215
  renderExpressionEditInput(expression: Expression) {
216
    const { expressionError } = this.props;
217
    const { inputValue, editing } = this.state;
218
    const error = editing === true && expressionError === true;
219
    return (
220
      <span className="expression-input-container" key={expression.input}>
221
        <form
222
          className="expression-input-form"
223
          onSubmit={(e: SyntheticEvent<HTMLFormElement>) =>
224
            this.handleExistingSubmit(e, expression)
225
          }
226
        >
227
          <input
228
            className={classnames("input-expression", { error })}
229
            type="text"
230
            onChange={this.handleChange}
231
            onBlur={this.clear}
232
            onKeyDown={this.handleKeyDown}
233
            value={editing ? inputValue : expression.input}
234
            ref={c => (this._input = c)}
235
          />
236
          <input type="submit" style={{ display: "none" }} />
237
        </form>
238
      </span>
239
    );
240
  }
241
242
  render() {
243
1x
    const { expressions } = this.props;
244
    return (
245
      <ul className="pane expressions-list">
246
2x
        {expressions.map(this.renderExpression)}
247
        {this.renderNewExpressionInput()}
248
      </ul>
249
    );
250
  }
251
}
252
253
3x
export default connect(
254
3x
  state => ({
255
5x
    expressions: getExpressions(state),
256
5x
    expressionError: getExpressionError(state)
257
2x
  }),
258
3x
  actions
259
2x
)(Expressions);
260