diff --git a/src/components/BranchMenu.tsx b/src/components/BranchMenu.tsx
index 56f5d9d3b..0dc2850a3 100644
--- a/src/components/BranchMenu.tsx
+++ b/src/components/BranchMenu.tsx
@@ -3,7 +3,10 @@ import { classes } from 'typestyle';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ClearIcon from '@material-ui/icons/Clear';
+import Modal from '@material-ui/core/Modal';
+import CircularProgress from '@material-ui/core/CircularProgress';
import { Dialog, showDialog, showErrorMessage } from '@jupyterlab/apputils';
+import { sleep } from '../utils';
import { Git, IGitExtension } from '../tokens';
import {
activeListItemClass,
@@ -17,11 +20,58 @@ import {
newBranchButtonClass,
wrapperClass
} from '../style/BranchMenu';
+import { fullscreenProgressClass } from '../style/progress';
import { NewBranchDialog } from './NewBranchDialog';
const CHANGES_ERR_MSG =
'The current branch contains files with uncommitted changes. Please commit or discard these changes before switching to or creating another branch.';
+/**
+ * Callback invoked upon encountering an error when switching branches.
+ *
+ * @private
+ * @param err - error
+ */
+function onBranchError(err: any): void {
+ if (err.message.includes('following files would be overwritten')) {
+ showDialog({
+ title: 'Unable to switch branch',
+ body: (
+
+
+ Your changes to the following files would be overwritten by
+ switching:
+
+
+ {err.message
+ .split('\n')
+ .slice(1, -3)
+ .map(renderFileName)}
+
+
+ Please commit, stash, or discard your changes before you switch
+ branches.
+
+
+ ),
+ buttons: [Dialog.okButton({ label: 'Dismiss' })]
+ });
+ } else {
+ showErrorMessage('Error switching branch', err.message);
+ }
+}
+
+/**
+ * Renders a file name.
+ *
+ * @private
+ * @param filename - file name
+ * @returns React element
+ */
+function renderFileName(filename: string): React.ReactElement {
+ return {filename};
+}
+
/**
* Interface describing component properties.
*/
@@ -35,6 +85,11 @@ export interface IBranchMenuProps {
* Boolean indicating whether branching is disabled.
*/
branching: boolean;
+
+ /**
+ * Boolean indicating whether to enable UI suspension.
+ */
+ suspend: boolean;
}
/**
@@ -60,6 +115,11 @@ export interface IBranchMenuState {
* Current list of branches.
*/
branches: Git.IBranch[];
+
+ /**
+ * Boolean indicating whether UI interaction should be suspended (e.g., due to pending command).
+ */
+ suspend: boolean;
}
/**
@@ -84,7 +144,8 @@ export class BranchMenu extends React.Component<
filter: '',
branchDialog: false,
current: repo ? this.props.model.currentBranch.name : '',
- branches: repo ? this.props.model.branches : []
+ branches: repo ? this.props.model.branches : [],
+ suspend: false
};
}
@@ -110,46 +171,65 @@ export class BranchMenu extends React.Component<
render(): React.ReactElement {
return (
-
-
-
- {this.state.filter ? (
-
- ) : null}
-
+ {this._renderFilter()}
+ {this._renderBranchList()}
+ {this._renderNewBranchDialog()}
+ {this._renderFeedback()}
+
+ );
+ }
+
+ /**
+ * Renders a branch input filter.
+ *
+ * @returns React element
+ */
+ private _renderFilter(): React.ReactElement {
+ return (
+
+
+ {this.state.filter ? (
+
+ ) : null}
-
- {this._renderItems()}
-
-
);
}
+ /**
+ * Renders a
+ *
+ * @returns React element
+ */
+ private _renderBranchList(): React.ReactElement {
+ return (
+
+ {this._renderItems()}
+
+ );
+ }
+
/**
* Renders menu items.
*
@@ -191,6 +271,37 @@ export class BranchMenu extends React.Component<
);
}
+ /**
+ * Renders a dialog for creating a new branch.
+ *
+ * @returns React element
+ */
+ private _renderNewBranchDialog(): React.ReactElement {
+ return (
+
+ );
+ }
+
+ /**
+ * Renders a component to provide UI feedback.
+ *
+ * @returns React element
+ */
+ private _renderFeedback(): React.ReactElement | null {
+ if (this.props.suspend === false || this.state.suspend === false) {
+ return null;
+ }
+ return (
+
+
+
+ );
+ }
+
/**
* Adds model listeners.
*/
@@ -221,6 +332,19 @@ export class BranchMenu extends React.Component<
});
}
+ /**
+ * Sets the suspension state.
+ *
+ * @param bool - boolean indicating whether to suspend UI interaction
+ */
+ private _suspend(bool: boolean): void {
+ if (this.props.suspend) {
+ this.setState({
+ suspend: bool
+ });
+ }
+ }
+
/**
* Callback invoked upon a change to the menu filter.
*
@@ -280,8 +404,10 @@ export class BranchMenu extends React.Component<
*
* @private
* @param event - event object
+ * @returns promise which resolves upon attempting to switch branches
*/
- function onClick(): void {
+ async function onClick(): Promise
{
+ let result: Array;
if (!self.props.branching) {
showErrorMessage('Switching branches is disabled', CHANGES_ERR_MSG);
return;
@@ -289,66 +415,30 @@ export class BranchMenu extends React.Component<
const opts = {
branchname: branch
};
- self.props.model
- .checkout(opts)
- .then(onResolve)
- .catch(onError);
- }
-
- /**
- * Callback invoked upon promise resolution.
- *
- * @private
- * @param result - result
- */
- function onResolve(result: any): void {
- if (result.code !== 0) {
- showErrorMessage('Error switching branch', result.message);
+ self._suspend(true);
+ try {
+ result = await Promise.all([
+ sleep(1000),
+ self.props.model.checkout(opts)
+ ]);
+ } catch (err) {
+ self._suspend(false);
+ return onBranchError(err);
}
- }
-
- /**
- * Callback invoked upon encountering an error.
- *
- * @private
- * @param err - error
- */
- function onError(err: any): void {
- if (err.message.includes('following files would be overwritten')) {
- showDialog({
- title: 'Unable to switch branch',
- body: (
-
-
- Your changes to the following files would be overwritten by
- switching:
-
-
- {err.message
- .split('\n')
- .slice(1, -3)
- .map(renderFileName)}
-
-
- Please commit, stash, or discard your changes before you switch
- branches.
-
-
- ),
- buttons: [Dialog.okButton({ label: 'Dismiss' })]
- });
- } else {
- showErrorMessage('Error switching branch', err.message);
+ self._suspend(false);
+ const res = result[1] as Git.ICheckoutResult;
+ if (res.code !== 0) {
+ showErrorMessage('Error switching branch', res.message);
}
}
-
- /**
- * Render a filename into a list
- * @param filename
- * @returns ReactElement
- */
- function renderFileName(filename: string): React.ReactElement {
- return {filename};
- }
}
+
+ /**
+ * Callback invoked upon clicking on the feedback modal.
+ *
+ * @param event - event object
+ */
+ private _onFeedbackModalClick = (): void => {
+ this._suspend(false);
+ };
}
diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx
index f46fdc126..e9393d540 100644
--- a/src/components/Toolbar.tsx
+++ b/src/components/Toolbar.tsx
@@ -299,6 +299,7 @@ export class Toolbar extends React.Component {
) : null}