@jupyterlab/git
Version:
A JupyterLab extension for version control using git
153 lines (152 loc) • 9.12 kB
JavaScript
import { Notification, UseSignal } from '@jupyterlab/apputils';
import { PageConfig, PathExt } from '@jupyterlab/coreutils';
import { caretDownIcon, caretUpIcon, refreshIcon } from '@jupyterlab/ui-components';
import Badge from '@mui/material/Badge';
import * as React from 'react';
import { showError } from '../notifications';
import { badgeClass, branchInfoClass, branchNameClass, repoBranchColumnClass, repoButtonClass, repoButtonLabelClass, repoLabelClass, spacer, toolbarButtonClass, toolbarClass, toolbarMenuWrapperClass, toolbarNavClass } from '../style/Toolbar';
import { branchIcon, desktopIcon, pullIcon, pushIcon } from '../style/icons';
import { CommandIDs, Git } from '../tokens';
import { ActionButton } from './ActionButton';
import { SubmoduleMenu } from './SubmoduleMenu';
export class Toolbar extends React.Component {
constructor(props) {
super(props);
this._onPullClick = async () => {
await this.props.commands.execute(CommandIDs.gitPull);
};
this._onPushClick = async () => {
await this.props.commands.execute(CommandIDs.gitPush);
};
this._onRepoClick = () => {
this.setState({ repoMenu: !this.state.repoMenu });
};
/**
* Callback invoked upon clicking a button to refresh the model.
*/
this._onRefreshClick = async () => {
const id = Notification.emit(this.props.trans.__('Refreshing…'), 'in-progress', { autoClose: false });
this.setState({ refreshInProgress: true });
try {
await this.props.model.refresh();
Notification.update({
id,
message: this.props.trans.__('Successfully refreshed.'),
type: 'success',
autoClose: 5000
});
}
catch (error) {
console.error(error);
Notification.update({
id,
message: this.props.trans.__('Failed to refresh.'),
type: 'error',
...showError(error, this.props.trans)
});
}
finally {
this.setState({ refreshInProgress: false });
}
};
this.state = {
refreshInProgress: false,
repoMenu: false
};
}
render() {
return (React.createElement(UseSignal, { signal: this.props.model.repositoryChanged }, () => {
if (this.props.model.pathRepository === null) {
return React.createElement("div", { className: toolbarClass });
}
return (React.createElement(UseSignal, { signal: this.props.model.headChanged }, () => (React.createElement(UseSignal, { signal: this.props.model.branchesChanged }, () => (React.createElement(UseSignal, { signal: this.props.model.statusChanged }, () => (React.createElement(UseSignal, { signal: this.props.model.submodulesChanged }, () => (React.createElement(UseSignal, { signal: this.props.model.remotesChanged }, () => (React.createElement("div", { className: toolbarClass },
this._renderToolbarRow(),
this.state.repoMenu
? this._renderSubmodules()
: null))))))))))));
}));
}
_renderToolbarRow() {
const hasSubmodules = this.props.model.submodules.length > 0;
return (React.createElement("div", { className: toolbarNavClass },
React.createElement("div", { className: repoBranchColumnClass },
hasSubmodules ? this._renderRepoButton() : this._renderRepoLabel(),
this._renderBranchInfo()),
React.createElement("span", { className: spacer }),
this._renderRemoteActions()));
}
_renderRepoLabel() {
const repositoryName = this._getRepositoryName();
return (React.createElement("span", { className: repoLabelClass, title: this.props.trans.__('Current repository: %1', this._getFullRepositoryPath()) },
React.createElement(desktopIcon.react, { tag: "span", className: "jp-Icon" }),
React.createElement("span", { className: repoButtonLabelClass }, repositoryName)));
}
_renderRepoButton() {
const repositoryName = this._getRepositoryName();
return (React.createElement("button", { type: "button", className: repoButtonClass, title: this.props.trans.__('Current repository: %1 — click to switch submodule', this._getFullRepositoryPath()), "aria-haspopup": "menu", "aria-expanded": this.state.repoMenu, onClick: this._onRepoClick },
React.createElement(desktopIcon.react, { tag: "span", className: "jp-Icon" }),
React.createElement("span", { className: repoButtonLabelClass }, repositoryName),
this.state.repoMenu ? (React.createElement(caretUpIcon.react, { tag: "span", className: "jp-Icon" })) : (React.createElement(caretDownIcon.react, { tag: "span", className: "jp-Icon" }))));
}
_renderBranchInfo() {
var _a;
const currentBranch = ((_a = this.props.model.currentBranch) === null || _a === void 0 ? void 0 : _a.name) || 'main';
let branchTitle;
switch (this.props.model.status.state) {
case Git.State.CHERRY_PICKING:
branchTitle = this.props.trans.__('Cherry-picking on %1', currentBranch);
break;
case Git.State.DETACHED:
branchTitle = this.props.trans.__('Detached HEAD at %1', currentBranch);
break;
case Git.State.MERGING:
branchTitle = this.props.trans.__('Merging on %1', currentBranch);
break;
case Git.State.REBASING:
branchTitle = this.props.trans.__('Rebasing %1', currentBranch);
break;
default:
branchTitle = this.props.trans.__('Current branch: %1', currentBranch);
}
return (React.createElement("span", { className: branchInfoClass, title: branchTitle },
React.createElement(branchIcon.react, { tag: "span", className: "jp-Icon" }),
React.createElement("span", { className: branchNameClass }, currentBranch)));
}
_renderRemoteActions() {
var _a;
const activeBranch = this.props.model.branches.filter(branch => branch.is_current_branch);
const hasRemote = this.props.model.remotes.length > 0;
const hasUpstream = ((_a = activeBranch[0]) === null || _a === void 0 ? void 0 : _a.upstream) !== null;
return (React.createElement(React.Fragment, null,
React.createElement(Badge, { className: badgeClass, variant: "dot", invisible: !hasRemote || this.props.model.status.behind === 0 },
React.createElement(ActionButton, { className: toolbarButtonClass, disabled: !hasRemote, icon: pullIcon, onClick: hasRemote ? this._onPullClick : undefined, title: hasRemote
? this.props.trans.__('Pull latest changes') +
(this.props.model.status.behind > 0
? this.props.trans.__(' (behind by %1 commits)', this.props.model.status.behind)
: '')
: this.props.trans.__('No remote repository defined') })),
React.createElement(Badge, { className: badgeClass, variant: "dot", invisible: !hasRemote || (this.props.model.status.ahead === 0 && hasUpstream) },
React.createElement(ActionButton, { className: toolbarButtonClass, disabled: !hasRemote, icon: pushIcon, onClick: hasRemote ? this._onPushClick : undefined, title: hasRemote
? hasUpstream
? this.props.trans.__('Push committed changes') +
(this.props.model.status.ahead > 0
? this.props.trans.__(' (ahead by %1 commits)', this.props.model.status.ahead)
: '')
: this.props.trans.__('Publish branch')
: this.props.trans.__('No remote repository defined') })),
React.createElement(ActionButton, { className: toolbarButtonClass, icon: refreshIcon, onClick: this._onRefreshClick, disabled: this.state.refreshInProgress, title: this.props.trans.__('Refresh the repository to detect local and remote changes') })));
}
_renderSubmodules() {
return (React.createElement("div", { className: toolbarMenuWrapperClass },
React.createElement(SubmoduleMenu, { model: this.props.model, submodules: this.props.model.submodules, trans: this.props.trans })));
}
_getRepositoryName() {
return (PathExt.basename(this.props.model.pathRepository || PageConfig.getOption('serverRoot')) || 'Jupyter Server Root');
}
_getFullRepositoryPath() {
var _a;
return (PageConfig.getOption('serverRoot') +
'/' +
((_a = this.props.model.pathRepository) !== null && _a !== void 0 ? _a : ''));
}
}