Skip to content

Commit bae7424

Browse files
authored
COMPASS-4294: Improve shell plugin interaction (#203)
1 parent cca9e6f commit bae7424

File tree

6 files changed

+84
-13
lines changed

6 files changed

+84
-13
lines changed

packages/browser-repl/src/components/ace-theme.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const foregroundColor = uiColors.gray.light3;
44
const backgroundColor = uiColors.gray.dark3;
55
const borderColor = uiColors.gray.dark1;
66
const activeLineColor = uiColors.gray.dark2;
7+
const selectionColor = uiColors.gray.dark1;
78

89
const cursorColor = uiColors.green.base;
910

@@ -40,7 +41,7 @@ const layoutCss = `
4041
background: transparent;
4142
}
4243
.ace-mongosh .ace_marker-layer .ace_selection {
43-
background: transparent;
44+
background: ${selectionColor};
4445
}
4546
`;
4647

packages/browser-repl/src/components/editor.spec.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,14 @@ describe('<Editor />', () => {
134134
execCommandBoundTo(aceEditor, 'Down');
135135
expect(spy).not.to.have.been.called;
136136
});
137+
138+
it('sets the input ref for the editor', () => {
139+
const spy = sinon.spy();
140+
const wrapper = mount(<Editor setInputRef={spy} />);
141+
142+
const aceEditor = getAceEditorInstance(wrapper);
143+
144+
expect(spy).to.have.been.calledOnce;
145+
expect(spy.args[0][0].editor).to.equal(aceEditor);
146+
});
137147
});

packages/browser-repl/src/components/editor.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface EditorProps {
2121
onArrowDownOnLastLine?(): void | Promise<void>;
2222
onChange?(value: string): void | Promise<void>;
2323
autocompleter?: Autocompleter;
24+
setInputRef?(ref): void;
2425
value?: string;
2526
}
2627

@@ -30,6 +31,7 @@ export class Editor extends Component<EditorProps> {
3031
onArrowUpOnFirstLine: PropTypes.func,
3132
onArrowDownOnLastLine: PropTypes.func,
3233
onChange: PropTypes.func,
34+
setInputRef: PropTypes.func,
3335
value: PropTypes.string
3436
};
3537

@@ -71,6 +73,11 @@ export class Editor extends Component<EditorProps> {
7173
}}
7274
name={`mongosh-ace-${Date.now()}`}
7375
mode="javascript"
76+
ref={(ref: any): void => {
77+
if (this.props.setInputRef) {
78+
this.props.setInputRef(ref);
79+
}
80+
}}
7481
theme="mongosh"
7582
onChange={this.props.onChange}
7683
onLoad={this.onEditorLoad}

packages/browser-repl/src/components/shell-input.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ interface ShellInputProps {
1212
onInput?(code: string): void | Promise<void>;
1313
history?: readonly string[];
1414
autocompleter?: Autocompleter;
15+
setInputRef?(ref): void;
1516
}
1617

1718
interface ShellInputState {
@@ -22,7 +23,8 @@ export class ShellInput extends Component<ShellInputProps, ShellInputState> {
2223
static propTypes = {
2324
onInput: PropTypes.func,
2425
history: PropTypes.arrayOf(PropTypes.string),
25-
autocompleter: PropTypes.object
26+
autocompleter: PropTypes.object,
27+
setInputRef: PropTypes.func
2628
};
2729

2830
readonly state: ShellInputState = {
@@ -120,6 +122,7 @@ export class ShellInput extends Component<ShellInputProps, ShellInputState> {
120122
onArrowUpOnFirstLine={this.historyBack}
121123
onArrowDownOnLastLine={this.historyNext}
122124
autocompleter={this.props.autocompleter}
125+
setInputRef={this.props.setInputRef}
123126
/>);
124127

125128
const className = classnames(styles['shell-input']);

packages/browser-repl/src/components/shell.spec.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { ShellInput } from './shell-input';
77
import { ShellOutput } from './shell-output';
88
import { ShellOutputEntry } from './shell-output-line';
99

10+
const styles = require('./shell.less');
11+
1012
const wait: (ms?: number) => Promise<void> = (ms = 10) => {
1113
return new Promise((resolve) => setTimeout(resolve, ms));
1214
};
@@ -17,6 +19,7 @@ describe('<Shell />', () => {
1719
let fakeRuntime;
1820
let wrapper: ShallowWrapper | ReactWrapper;
1921
let scrollIntoView;
22+
let elementFocus;
2023
let onInput;
2124

2225
beforeEach(() => {
@@ -27,6 +30,7 @@ describe('<Shell />', () => {
2730
};
2831

2932
scrollIntoView = sinon.spy(Element.prototype, 'scrollIntoView');
33+
elementFocus = sinon.spy(HTMLElement.prototype, 'focus');
3034

3135
fakeRuntime = {
3236
evaluate: sinon.fake.returns({ value: 'some result' })
@@ -43,6 +47,7 @@ describe('<Shell />', () => {
4347

4448
afterEach(() => {
4549
scrollIntoView.restore();
50+
elementFocus.restore();
4651
});
4752

4853
it('renders a ShellOutput component', () => {
@@ -265,4 +270,30 @@ describe('<Shell />', () => {
265270

266271
expect(Element.prototype.scrollIntoView).to.have.been.calledTwice;
267272
});
273+
274+
it('focuses on the input when the background container is clicked', () => {
275+
wrapper = mount(<Shell runtime={fakeRuntime} />);
276+
const container = wrapper.find(`.${styles.shell}`);
277+
278+
const fakeMouseEvent: any = {
279+
target: 'a',
280+
currentTarget: 'a'
281+
};
282+
container.prop('onClick')(fakeMouseEvent);
283+
284+
expect(HTMLElement.prototype.focus).to.have.been.calledOnce;
285+
});
286+
287+
it('does not focus on the input when an element that is not the background container is clicked', () => {
288+
wrapper = mount(<Shell runtime={fakeRuntime} />);
289+
const container = wrapper.find(`.${styles.shell}`);
290+
291+
const fakeMouseEvent: any = {
292+
target: 'a',
293+
currentTarget: 'b'
294+
};
295+
container.prop('onClick')(fakeMouseEvent);
296+
297+
expect(HTMLElement.prototype.focus).to.not.have.been.called;
298+
});
268299
});

packages/browser-repl/src/components/shell.tsx

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export class Shell extends Component<ShellProps, ShellState> {
9898
};
9999

100100
private shellInputElement?: HTMLElement;
101+
private shellInputRef?: {
102+
editor?: HTMLElement;
103+
};
101104

102105
readonly state: ShellState = {
103106
output: this.props.initialOutput.slice(-this.props.maxOutputLength),
@@ -188,18 +191,34 @@ export class Shell extends Component<ShellProps, ShellState> {
188191
this.shellInputElement.scrollIntoView();
189192
}
190193

194+
private onShellClicked = (event: React.MouseEvent): void => {
195+
// Focus on input when clicking the shell background (not clicking output).
196+
if (event.currentTarget === event.target) {
197+
if (this.shellInputRef && this.shellInputRef.editor) {
198+
this.shellInputRef.editor.focus();
199+
}
200+
}
201+
};
202+
191203
render(): JSX.Element {
192-
return (<div className={classnames(styles.shell)}>
193-
<div>
194-
<ShellOutput
195-
output={this.state.output} />
196-
</div>
197-
<div ref={(el): void => { this.shellInputElement = el; }}>
198-
<ShellInput
199-
onInput={this.onInput}
200-
history={this.state.history}
201-
autocompleter={this.props.runtime} />
204+
return (
205+
<div
206+
className={classnames(styles.shell)}
207+
onClick={this.onShellClicked}
208+
>
209+
<div>
210+
<ShellOutput
211+
output={this.state.output} />
212+
</div>
213+
<div ref={(el): void => { this.shellInputElement = el; }}>
214+
<ShellInput
215+
onInput={this.onInput}
216+
history={this.state.history}
217+
autocompleter={this.props.runtime}
218+
setInputRef={(ref): void => { this.shellInputRef = ref;}}
219+
/>
220+
</div>
202221
</div>
203-
</div>);
222+
);
204223
}
205224
}

0 commit comments

Comments
 (0)