@jupyterlab/git
Version:
A JupyterLab extension for version control using git
197 lines (196 loc) • 7.3 kB
JavaScript
import { ReactWidget, UseSignal } from '@jupyterlab/apputils';
import Badge from '@mui/material/Badge';
import React from 'react';
import { classes } from 'typestyle';
import { Operation, showGitOperationDialog } from '../commandsAndMenu';
import { gitIcon } from '../style/icons';
import { badgeClass, statusAnimatedIconClass, statusIconClass, currentBranchNameClass } from '../style/StatusWidget';
import { toolbarButtonClass } from '../style/Toolbar';
import { sleep } from '../utils';
import { ActionButton } from './ActionButton';
export class StatusWidget extends ReactWidget {
/**
* Returns a status bar widget.
* @param trans - The language translator
* @returns widget
*/
constructor(model, trans) {
super();
/**
* Boolean indicating whether the status widget is accepting updates.
*/
this._locked = false;
/**
* Status string.
*/
this._status = '';
this._model = model;
this._trans = trans;
this.addClass('jp-git-StatusWidget');
}
/**
* Sets the current status.
*/
set status(text) {
this._status = text;
if (!this._locked) {
this._animate();
}
}
render() {
return (React.createElement(React.Fragment, null,
React.createElement(UseSignal, { signal: this._model.credentialsRequiredChanged, initialArgs: false }, (_, needsCredentials) => (React.createElement(Badge, { className: badgeClass, variant: "dot", invisible: !needsCredentials, "data-test-id": "git-credential-badge" },
React.createElement(ActionButton, { className: classes(toolbarButtonClass, this._status !== 'idle'
? statusAnimatedIconClass
: statusIconClass), icon: gitIcon, onClick: needsCredentials
? async () => this._showGitOperationDialog()
: undefined, title: needsCredentials
? `Git: ${this._trans.__('credentials required')}`
: `Git: ${this._trans.__(this._status)}` })))),
React.createElement(UseSignal, { signal: this._model.headChanged }, () => this._model.currentBranch && (React.createElement("span", { className: currentBranchNameClass }, this._model.currentBranch.name)))));
}
async _showGitOperationDialog() {
try {
await showGitOperationDialog(this._model, Operation.Fetch, this._trans);
}
catch (error) {
console.error('Encountered an error when fetching. Error:', error);
}
}
/**
* Locks the status widget to prevent updates.
*
* ## Notes
*
* - This is used to throttle updates in order to prevent "flashing" messages.
*/
async _animate() {
this._locked = true;
this.update();
await sleep(500);
this._locked = false;
this.update();
}
}
export function addStatusBarWidget(statusBar, model, settings, trans) {
// Add a status bar widget to provide Git status updates:
const statusWidget = new StatusWidget(model, trans);
statusBar.registerStatusItem('git-status', {
align: 'left',
item: statusWidget,
isActive: Private.isStatusWidgetActive(settings),
activeStateChanged: settings && settings.changed
});
const callback = Private.createEventCallback(statusWidget);
model.taskChanged.connect(callback);
statusWidget.disposed.connect(() => {
model.taskChanged.disconnect(callback);
});
}
var Private;
(function (Private) {
/**
* Returns a callback for updating a status widget upon receiving model events.
*
* @private
* @param widget - status widget
* @returns callback
*/
function createEventCallback(widget) {
return onEvent;
/**
* Callback invoked upon a model event.
*
* @private
* @param model - extension model
* @param event - event name
*/
function onEvent(model, event) {
let status;
switch (event) {
case 'empty':
status = 'idle';
break;
case 'git:checkout':
status = 'checking out…';
break;
case 'git:clone':
status = 'cloning repository…';
break;
case 'git:commit:create':
status = 'committing changes…';
break;
case 'git:commit:revert':
status = 'reverting changes…';
break;
case 'git:init':
status = 'initializing repository…';
break;
case 'git:merge':
status = 'merging…';
break;
case 'git:pull':
status = 'pulling changes…';
break;
case 'git:pushing':
status = 'pushing changes…';
break;
case 'git:rebase':
status = 'rebasing…';
break;
case 'git:rebase:resolve':
status = 'resolving rebase…';
break;
case 'git:refresh':
status = 'refreshing…';
break;
case 'git:reset:changes':
status = 'resetting changes…';
break;
case 'git:reset:hard':
status = 'discarding changes…';
break;
default:
if (/git:add:files/.test(event)) {
status = 'adding files…';
}
else {
status = 'working…';
}
break;
}
widget.status = status;
}
}
Private.createEventCallback = createEventCallback;
/**
* Returns a callback which returns a boolean indicating whether the extension should display status updates.
*
* @private
* @param settings - extension settings
* @returns callback
*/
function isStatusWidgetActive(settings) {
return settings ? isActive : inactive;
/**
* Returns a boolean indicating that the extension should not display status updates.
*
* @private
* @returns boolean indicating that the extension should not display status updates
*/
function inactive() {
return false;
}
/**
* Returns a boolean indicating whether the extension should display status updates.
*
* @private
* @returns boolean indicating whether the extension should display status updates
*/
function isActive() {
var _a;
return ((_a = settings === null || settings === void 0 ? void 0 : settings.composite.displayStatus) !== null && _a !== void 0 ? _a : true);
}
}
Private.isStatusWidgetActive = isStatusWidgetActive;
})(Private || (Private = {}));