UNPKG

@jupyterlab/git

Version:

A JupyterLab extension for version control using git

153 lines (152 loc) 9.12 kB
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 : '')); } }