UNPKG

@jupyterlab/git

Version:

A JupyterLab extension for version control using git

199 lines (198 loc) 7.39 kB
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); }); } /* eslint-disable no-inner-declarations */ 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 = {})); /* eslint-enable no-inner-declarations */