@jupyterlab/git
Version:
A JupyterLab extension for version control using git
144 lines (143 loc) • 7.52 kB
JavaScript
import { checkIcon } from '@jupyterlab/ui-components';
import { CommandRegistry } from '@lumino/commands';
import Button from '@mui/material/Button';
import ButtonGroup from '@mui/material/ButtonGroup';
import ClickAwayListener from '@mui/material/ClickAwayListener';
import Grow from '@mui/material/Grow';
import MenuItem from '@mui/material/MenuItem';
import MenuList from '@mui/material/MenuList';
import Paper from '@mui/material/Paper';
import Popper from '@mui/material/Popper';
import * as React from 'react';
import { classes } from 'typestyle';
import { listItemIconClass } from '../style/BranchMenu';
import { commitButtonClass, commitFormClass, commitPaperClass, commitRoot, commitVariantSelector, disabledStyle } from '../style/CommitBox';
import { verticalMoreIcon } from '../style/icons';
import { listItemBoldTitleClass, listItemContentClass, listItemDescClass } from '../style/NewBranchDialog';
import { CommandIDs } from '../tokens';
import { CommitMessage } from './CommitMessage';
/**
* React component for entering a commit message.
*/
export class CommitBox extends React.Component {
/**
* Returns a React component for entering a commit message.
*
* @param props - component properties
* @returns React component
*/
constructor(props) {
super(props);
/**
* Get keystroke configured to act as a submit action.
*/
this._getSubmitKeystroke = () => {
var _a;
const binding = this.props.commands.keyBindings.find(binding => binding.command === CommandIDs.gitSubmitCommand);
return (_a = binding === null || binding === void 0 ? void 0 : binding.keys.join(' ')) !== null && _a !== void 0 ? _a : '';
};
/**
* Close the commit variant menu if needed.
*/
this._handleClose = (event) => {
if (this._anchorRef.current &&
this._anchorRef.current.contains(event.target)) {
return;
}
this.setState({ open: false });
};
/**
* Handle commit variant menu item click
*/
this._handleMenuItemClick = (event, index) => {
this.setState({
open: false
});
this.props.setAmend(index === 1);
};
/**
* Toggle state of the commit variant menu visibility
*/
this._handleToggle = () => {
this.setState({ open: !this.state.open });
};
/**
* Callback invoked upon command execution activated when entering a commit message description.
*
* ## Notes
*
* - Triggers the `'submit'` action on appropriate command (and if commit is possible)
*
*/
this._handleCommand = (_, commandArgs) => {
if (commandArgs.id === CommandIDs.gitSubmitCommand && this._canCommit()) {
this.props.onCommit();
}
};
this._options = [];
this._options.push({
title: this.props.trans.__('Create a new commit'),
description: this.props.trans.__('New commit will be created and show up as a next one after the previous commit (default).')
}, {
title: this.props.trans.__('Amend previous commit'),
description: this.props.trans.__('Staged changes will be added to the previous commit and its date will be updated.')
});
this._anchorRef = React.createRef();
this.state = {
open: false
};
}
componentDidMount() {
this.props.commands.commandExecuted.connect(this._handleCommand);
}
componentWillUnmount() {
this.props.commands.commandExecuted.disconnect(this._handleCommand);
}
/**
* Renders the component.
*
* @returns React element
*/
render() {
var _a;
const disabled = !this._canCommit();
const title = !this.props.hasFiles
? this.props.trans.__('Disabled: No files are staged for commit')
: !this.props.summary && !this.props.amend
? this.props.trans.__('Disabled: No commit message summary')
: this.props.label;
const shortcutHint = CommandRegistry.formatKeystroke(this._getSubmitKeystroke());
const summaryPlaceholder = this.props.trans.__('Summary (%1 to commit)', shortcutHint);
return (React.createElement("div", { className: classes(commitFormClass, 'jp-git-CommitBox') }, (_a = this.props.warning) !== null && _a !== void 0 ? _a : null,
!this.props.amend && (React.createElement(CommitMessage, { error: this.props.hasFiles &&
!this.props.amend &&
this.props.summary.length === 0, trans: this.props.trans, summary: this.props.summary, summaryPlaceholder: summaryPlaceholder, description: this.props.description, setSummary: this.props.setSummary, setDescription: this.props.setDescription })),
React.createElement(ButtonGroup, { ref: this._anchorRef, fullWidth: true, size: "small" },
React.createElement(Button, { classes: {
root: commitButtonClass,
disabled: disabledStyle
}, title: title, disabled: disabled, onClick: this.props.onCommit }, this.props.label),
React.createElement(Button, { classes: {
root: classes(commitButtonClass, commitVariantSelector),
disabled: disabledStyle
}, size: "small", disabled: !this.props.hasFiles, "aria-controls": this.state.open ? 'split-button-menu' : undefined, "aria-expanded": this.state.open ? 'true' : undefined, "aria-label": "select commit variant", "aria-haspopup": "menu", onClick: this._handleToggle },
React.createElement(verticalMoreIcon.react, { tag: "span" }))),
React.createElement(Popper, { open: this.state.open, anchorEl: this._anchorRef.current, role: undefined, transition: true, disablePortal: true }, ({ TransitionProps }) => (React.createElement(Grow, { ...TransitionProps },
React.createElement(Paper, { classes: { root: classes(commitRoot, commitPaperClass) } },
React.createElement(ClickAwayListener, { onClickAway: this._handleClose },
React.createElement(MenuList, { id: "split-button-menu" }, this._options.map((option, index) => (React.createElement(MenuItem, { key: option.title, classes: { root: commitRoot }, selected: this.props.amend ? index === 1 : index === 0, onClick: event => this._handleMenuItemClick(event, index) },
(this.props.amend ? index === 1 : index === 0) ? (React.createElement(checkIcon.react, { className: listItemIconClass, tag: "span" })) : (React.createElement("span", { className: listItemIconClass })),
React.createElement("div", { className: listItemContentClass },
React.createElement("p", { className: listItemBoldTitleClass }, option.title),
React.createElement("p", { className: listItemDescClass }, option.description)))))))))))));
}
/**
* Whether a commit can be performed (files are staged and summary is not empty).
*/
_canCommit() {
if (this.props.amend) {
return this.props.hasFiles;
}
return !!(this.props.hasFiles && this.props.summary);
}
}