Skip to content

Commit b5337ca

Browse files
authored
feat: add compass shell progress indicator COMPASS-4515 (#666)
1 parent 0260865 commit b5337ca

File tree

13 files changed

+216
-74
lines changed

13 files changed

+216
-74
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { shallow, mount } from '../../testing/enzyme';
55

66
import { ShellInput } from './shell-input';
77
import { Editor } from './editor';
8-
import Loader from './shell-loader';
8+
import ShellLoader from './shell-loader';
99

1010
function changeValue(wrapper, value): void {
1111
wrapper.find(Editor).prop('onChange')(value);
@@ -117,7 +117,7 @@ describe('<ShellInput />', () => {
117117
operationInProgress
118118
/>);
119119

120-
expect(wrapper.find(Loader).exists()).to.equal(true);
120+
expect(wrapper.find(ShellLoader).exists()).to.equal(true);
121121
});
122122

123123
it('does not show a loader when operationInProgress is false', () => {
@@ -126,7 +126,7 @@ describe('<ShellInput />', () => {
126126
operationInProgress={false}
127127
/>);
128128

129-
expect(wrapper.find(Loader).exists()).to.equal(false);
129+
expect(wrapper.find(ShellLoader).exists()).to.equal(false);
130130
});
131131
});
132132

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Autocompleter } from '@mongosh/browser-runtime-core';
33
import classnames from 'classnames';
44
import React, { Component } from 'react';
55
import { Editor } from './editor';
6-
import Loader from './shell-loader';
6+
import ShellLoader from './shell-loader';
77
import { LineWithIcon } from './utils/line-with-icon';
88

99
const styles = require('./shell-input.less');
@@ -109,9 +109,7 @@ export class ShellInput extends Component<ShellInputProps, ShellInputState> {
109109
render(): JSX.Element {
110110
let prompt: JSX.Element;
111111
if (this.props.operationInProgress) {
112-
prompt = (<Loader
113-
size={12}
114-
/>);
112+
prompt = (<ShellLoader />);
115113
} else if (this.props.prompt) {
116114
const trimmed = this.props.prompt.trim();
117115
if (trimmed.endsWith('>')) {

packages/browser-repl/src/components/shell-loader.less

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
@import '~@leafygreen-ui/palette/dist/ui-colors.less';
22

33
.shell-loader {
4-
border: 2px solid @leafygreen__gray--light-3;
5-
border-top: 2px solid @leafygreen__green--base;
4+
border: 2px solid transparent;
5+
border-top: 2px solid @leafygreen__green--light-2;
66
border-radius: 50%;
77
padding: 0;
88
margin: 0;
99
box-sizing: border-box;
10+
display: inline-block;
1011

11-
animation: shell-loader-spin 500ms linear infinite;
12+
animation: shell-loader-spin 700ms ease infinite;
1213
}
1314

1415
@keyframes shell-loader-spin {

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,31 @@ import classnames from 'classnames';
44
const styles = require('./shell-loader.less');
55

66
interface ShellLoaderProps {
7-
size: number;
7+
className: string;
8+
size?: string;
89
}
910

1011
export default class ShellLoader extends Component<ShellLoaderProps> {
12+
static defaultProps = {
13+
className: '',
14+
size: '12px'
15+
};
16+
1117
render(): JSX.Element {
12-
const { size } = this.props;
18+
const {
19+
className,
20+
size
21+
} = this.props;
1322

1423
return (
1524
<div
16-
className={classnames(styles['shell-loader'])}
25+
className={classnames(
26+
className,
27+
styles['shell-loader']
28+
)}
1729
style={{
18-
height: `${size}px`,
19-
width: `${size}px`
30+
width: size,
31+
height: size
2032
}}
2133
/>
2234
);

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const wait: (ms?: number) => Promise<void> = (ms = 10) => {
1717
describe('<Shell />', () => {
1818
let onOutputChangedSpy;
1919
let onHistoryChangedSpy;
20+
let onOperationStartedSpy;
21+
let onOperationEndSpy;
2022
let fakeRuntime;
2123
let wrapper: ShallowWrapper | ReactWrapper;
2224
let scrollIntoView;
@@ -40,11 +42,16 @@ describe('<Shell />', () => {
4042

4143
onOutputChangedSpy = sinon.spy();
4244
onHistoryChangedSpy = sinon.spy();
45+
onOperationStartedSpy = sinon.spy();
46+
onOperationEndSpy = sinon.spy();
4347

4448
wrapper = shallow(<Shell
4549
runtime={fakeRuntime}
4650
onOutputChanged={onOutputChangedSpy}
47-
onHistoryChanged={onHistoryChangedSpy} />);
51+
onHistoryChanged={onHistoryChangedSpy}
52+
onOperationStarted={onOperationStartedSpy}
53+
onOperationEnd={onOperationEndSpy}
54+
/>);
4855
});
4956

5057
afterEach(() => {
@@ -215,6 +222,14 @@ describe('<Shell />', () => {
215222
await onInput('db.createUser()');
216223
expect(wrapper.state('history')).to.deep.equal([]);
217224
});
225+
226+
it('calls onOperationStarted', async() => {
227+
expect(onOperationStartedSpy).to.have.been.calledOnce;
228+
});
229+
230+
it('calls onOperationEnd', async() => {
231+
expect(onOperationEndSpy).to.have.been.calledOnce;
232+
});
218233
});
219234

220235
context('when empty input is entered', () => {
@@ -316,6 +331,10 @@ describe('<Shell />', () => {
316331
it('calls onHistoryChanged', () => {
317332
expect(onHistoryChangedSpy).to.have.been.calledOnceWith(['some code']);
318333
});
334+
335+
it('calls onOperationEnd', async() => {
336+
expect(onOperationEndSpy).to.have.been.calledOnce;
337+
});
319338
});
320339

321340
it('scrolls the container to the bottom each time the output is updated', () => {

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ interface ShellProps {
4040
*/
4141
maxHistoryLength: number;
4242

43+
/* A function called when an operation has begun.
44+
*/
45+
onOperationStarted: () => void;
46+
47+
/* A function called when an operation has completed (both error and success).
48+
*/
49+
onOperationEnd: () => void;
50+
4351
/* An array of entries to be displayed in the output area.
4452
*
4553
* Can be used to restore the output between sessions, or to setup
@@ -68,16 +76,16 @@ interface ShellState {
6876
shellPrompt: string;
6977
}
7078

71-
const noop = (): void => {
72-
//
73-
};
79+
const noop = (): void => { /* */ };
7480

7581
/**
7682
* The browser-repl Shell component
7783
*/
7884
export class Shell extends Component<ShellProps, ShellState> {
7985
static defaultProps = {
8086
onHistoryChanged: noop,
87+
onOperationStarted: noop,
88+
onOperationEnd: noop,
8189
onOutputChanged: noop,
8290
maxOutputLength: 1000,
8391
maxHistoryLength: 1000,
@@ -114,6 +122,8 @@ export class Shell extends Component<ShellProps, ShellState> {
114122
let outputLine: ShellOutputEntry;
115123

116124
try {
125+
this.props.onOperationStarted();
126+
117127
this.props.runtime.setEvaluationListener(this);
118128
const result = await this.props.runtime.evaluate(code);
119129
outputLine = {
@@ -128,6 +138,7 @@ export class Shell extends Component<ShellProps, ShellState> {
128138
};
129139
} finally {
130140
await this.updateShellPrompt();
141+
this.props.onOperationEnd();
131142
}
132143

133144
return outputLine;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1+
import ShellLoader from './components/shell-loader';
2+
13
export { Shell } from './components/shell';
24
export { IframeRuntime } from './iframe-runtime';
5+
export { ShellLoader };

packages/compass-shell/src/components/compass-shell/compass-shell.jsx

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export class CompassShell extends Component {
4545

4646
this.state = {
4747
initialHistory: this.props.historyStorage ? null : [],
48-
isExpanded: !!this.props.isExpanded
48+
isExpanded: !!this.props.isExpanded,
49+
isOperationInProgress: false
4950
};
5051
}
5152

@@ -57,6 +58,18 @@ export class CompassShell extends Component {
5758
this.shellOutput = output;
5859
}
5960

61+
onOperationStarted = () => {
62+
this.setState({
63+
isOperationInProgress: true
64+
});
65+
}
66+
67+
onOperationEnd = () => {
68+
this.setState({
69+
isOperationInProgress: false
70+
});
71+
}
72+
6073
lastOpenHeight = defaultShellHeightOpened;
6174
resizableRef = null;
6275

@@ -123,7 +136,8 @@ export class CompassShell extends Component {
123136
*/
124137
render() {
125138
const {
126-
isExpanded
139+
isExpanded,
140+
isOperationInProgress
127141
} = this.state;
128142

129143
if (!this.props.runtime || !this.state.initialHistory) {
@@ -154,20 +168,25 @@ export class CompassShell extends Component {
154168
<ShellHeader
155169
isExpanded={isExpanded}
156170
onShellToggleClicked={this.shellToggleClicked}
171+
isOperationInProgress={isOperationInProgress}
157172
/>
158-
{isExpanded && (
159-
<div
160-
className={classnames(styles['compass-shell-shell-container'])}
161-
>
162-
<Shell
163-
runtime={this.props.runtime}
164-
initialHistory={this.state.initialHistory}
165-
initialOutput={this.shellOutput}
166-
onHistoryChanged={this.saveHistory}
167-
onOutputChanged={this.onShellOutputChanged}
168-
/>
169-
</div>
170-
)}
173+
<div
174+
className={classnames(
175+
styles['compass-shell-shell-container'], {
176+
[styles['compass-shell-shell-container-visible']]: isExpanded
177+
}
178+
)}
179+
>
180+
<Shell
181+
runtime={this.props.runtime}
182+
initialHistory={this.state.initialHistory}
183+
initialOutput={this.shellOutput}
184+
onHistoryChanged={this.saveHistory}
185+
onOutputChanged={this.onShellOutputChanged}
186+
onOperationStarted={this.onOperationStarted}
187+
onOperationEnd={this.onOperationEnd}
188+
/>
189+
</div>
171190
</Resizable>
172191
</Fragment>
173192
);

packages/compass-shell/src/components/compass-shell/compass-shell.less

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@
1212

1313
&-shell-container {
1414
flex-grow: 1;
15-
display: flex;
15+
display: none;
1616
overflow: auto;
1717
border-top: 1px solid @leafygreen__gray--dark-2;
18+
19+
&-visible {
20+
display: flex;
21+
}
1822
}
1923
}

packages/compass-shell/src/components/compass-shell/compass-shell.spec.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CompassShell } from './compass-shell';
88
import ResizeHandle from '../resize-handle';
99
import ShellHeader from '../shell-header';
1010
import InfoModal from '../info-modal';
11+
import styles from './compass-shell.less';
1112

1213
function updateAndWaitAsync(wrapper) {
1314
wrapper.update();
@@ -16,10 +17,13 @@ function updateAndWaitAsync(wrapper) {
1617

1718
describe('CompassShell', () => {
1819
context('when the prop isExpanded is false', () => {
19-
it('does not render a shell', () => {
20+
it('has the shell display none', () => {
2021
const fakeRuntime = {};
21-
const wrapper = shallow(<CompassShell runtime={fakeRuntime} isExpanded={false} />);
22-
expect(wrapper.find(Shell).exists()).to.equal(false);
22+
const wrapper = shallow(<CompassShell
23+
runtime={fakeRuntime}
24+
isExpanded={false}
25+
/>);
26+
expect(wrapper.find(`.${styles['compass-shell-shell-container-visible']}`).exists()).to.equal(false);
2327
});
2428

2529
context('when is it expanded', () => {
@@ -59,6 +63,7 @@ describe('CompassShell', () => {
5963
const fakeRuntime = {};
6064
const wrapper = shallow(<CompassShell runtime={fakeRuntime} isExpanded />);
6165
expect(wrapper.find(Shell).prop('runtime')).to.equal(fakeRuntime);
66+
expect(wrapper.find(`.${styles['compass-shell-shell-container-visible']}`).exists()).to.equal(true);
6267
});
6368

6469
it('renders the ShellHeader component', () => {

0 commit comments

Comments
 (0)