@jupyterlab/git
Version:
A JupyterLab extension for version control using git
190 lines (189 loc) • 10 kB
JavaScript
import { Notification } from '@jupyterlab/apputils';
import ClearIcon from '@mui/icons-material/Clear';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import * as React from 'react';
import { classes } from 'typestyle';
import { actionsWrapperClass, branchDialogClass, buttonClass, cancelButtonClass, closeButtonClass, contentWrapperClass, createButtonClass, errorMessageClass, filterClass, filterClearClass, filterInputClass, filterWrapperClass, nameInputClass, titleClass, titleWrapperClass } from '../style/NewBranchDialog';
import { activeListItemClass, commitBodyClass, commitHeaderBoldClass, commitHeaderClass, commitHeaderItemClass, commitItemBoldClass, commitWrapperClass, historyDialogBoxStyle, historyDialogBoxWrapperStyle } from '../style/NewTagDialog';
import { Git } from '../tokens';
import { GitCommitGraph } from './GitCommitGraph';
export const DialogBoxCommitGraph = (props) => {
const { filter, baseCommitId, updateBaseCommitId } = props;
const pastCommits = props.pastCommits
.filter(commit => !filter ||
commit.commit_msg.toLowerCase().includes(filter.toLowerCase()))
.slice();
const [nodeHeights, setNodeHeights] = React.useState({});
const nodes = React.useRef({});
React.useEffect(() => {
const resizeObserver = new ResizeObserver(entries => {
for (const entry of entries) {
const borderBoxSize = Array.isArray(entry.borderBoxSize)
? entry.borderBoxSize[0]
: entry.borderBoxSize;
const offsetHeight = borderBoxSize.blockSize;
const sha = entry.target.id;
setNodeHeights(prev => ({ ...prev, [sha]: offsetHeight }));
}
});
pastCommits.forEach(commit => {
const node = nodes.current[commit.commit];
resizeObserver.observe(node, {
box: 'border-box'
});
});
return () => resizeObserver.disconnect();
}, [pastCommits]);
let isFilter;
if (filter === '') {
isFilter = true;
}
if (props.isSingleCommit === true) {
isFilter = false;
}
return (React.createElement("div", { className: historyDialogBoxWrapperStyle },
isFilter && (React.createElement(GitCommitGraph, { commits: pastCommits.map(commit => ({
sha: commit.commit,
parents: commit.pre_commits
})), getNodeHeight: (sha) => { var _a; return (_a = nodeHeights[sha]) !== null && _a !== void 0 ? _a : 55; } })),
React.createElement("ol", { className: historyDialogBoxStyle }, pastCommits.map((commit, index) => {
return (React.createElement(SingleCommitNode, { key: index, commit: commit, baseCommitId: baseCommitId, trans: props.trans, setRef: node => {
nodes.current[commit.commit] = node;
}, updateBaseCommitId: updateBaseCommitId }));
}))));
};
export const SingleCommitNode = (props) => {
const { baseCommitId, updateBaseCommitId } = props;
const isLatest = props.commit.commit === baseCommitId;
let isBold;
if (isLatest) {
isBold = true;
}
/**
* Returns a callback which is invoked upon clicking a commit name.
*
* @param commit - commit
* @returns callback
*/
const onCommitClickFactory = React.useCallback((commitId) => {
return onClick;
/**
* Callback invoked upon clicking a commit.
*
* @private
* @param event - event object
*/
function onClick() {
updateBaseCommitId(commitId);
}
}, [baseCommitId]);
return (React.createElement("li", { id: props.commit.commit, ref: props.setRef, className: classes(commitWrapperClass, isLatest ? activeListItemClass : null), title: props.trans.__('Create a new tag pointing to commit %1: %2 by %3', props.commit.commit in Git.Diff.SpecialRef
? Git.Diff.SpecialRef[+props.commit.commit]
: props.commit.commit.slice(0, 7), props.commit.commit_msg, props.commit.author), onClick: onCommitClickFactory(props.commit.commit) },
React.createElement("div", { className: commitHeaderClass },
React.createElement("span", { className: classes(commitHeaderItemClass, isBold ? commitItemBoldClass : null) }, props.commit.author),
React.createElement("span", { className: classes(commitHeaderItemClass, isBold ? commitItemBoldClass : null) }, +props.commit.commit in Git.Diff.SpecialRef
? Git.Diff.SpecialRef[+props.commit.commit]
: props.commit.commit.slice(0, 7)),
React.createElement("span", { className: classes(commitHeaderItemClass, isBold ? commitItemBoldClass : null) }, props.commit.date)),
React.createElement("div", { className: classes(commitBodyClass, isBold ? commitHeaderBoldClass : null) }, props.commit.commit_msg)));
};
export const NewTagDialogBox = (props) => {
var _a, _b;
const [nameState, setNameState] = React.useState('');
const [baseCommitIdState, setBaseCommitIdState] = React.useState((_b = (_a = props.pastCommits[0]) === null || _a === void 0 ? void 0 : _a.commit) !== null && _b !== void 0 ? _b : null);
const [filterState, setFilterState] = React.useState('');
const [errorState, setErrorState] = React.useState('');
/**
* Callback invoked upon closing the dialog.
*
* @param event - event object
*/
const onClose = () => {
props.onClose();
setNameState('');
setFilterState('');
setErrorState('');
};
/**
* Callback invoked upon a change to the menu filter.
*
* @param event - event object
*/
const onFilterChange = React.useCallback((event) => {
setFilterState(event.target.value);
}, [filterState]);
/**
* Callback invoked to reset the menu filter.
*/
const resetFilter = () => {
setFilterState('');
};
/**
* Callback invoked upon a change to the tag name input element.
*
* @param event - event object
*/
const onNameChange = React.useCallback((event) => {
setNameState(event.target.value);
setErrorState('');
}, [nameState]);
/**
* Creates a new tag.
*
* @param tag - tag name
* @returns promise which resolves upon attempting to create a new tag
*/
const createTag = async () => {
const tagName = nameState;
const baseCommitId = baseCommitIdState;
const id = Notification.emit(props.trans.__('Creating tag…'), 'in-progress', { autoClose: false });
try {
await props.model.setTag(tagName, baseCommitId);
}
catch (err) {
setErrorState(err.message.replace(/^fatal:/, ''));
Notification.update({
id,
message: props.trans.__('Failed to create tag.'),
type: 'error',
autoClose: false
});
return;
}
Notification.update({
id,
message: props.trans.__('Tag created.'),
type: 'success',
autoClose: 5000
});
// Close the tag dialog:
props.onClose();
setNameState('');
setFilterState('');
};
return (React.createElement(Dialog, { classes: {
paper: branchDialogClass
}, open: props.open, onClose: onClose },
React.createElement("div", { className: titleWrapperClass },
React.createElement("p", { className: titleClass }, props.trans.__('Create a Tag')),
React.createElement("button", { className: closeButtonClass },
React.createElement(ClearIcon, { titleAccess: props.trans.__('Close this dialog'), fontSize: "small", onClick: onClose }))),
React.createElement("div", { className: contentWrapperClass },
errorState ? React.createElement("p", { className: errorMessageClass }, errorState) : null,
React.createElement("p", null, props.trans.__('Name')),
React.createElement("input", { className: nameInputClass, type: "text", onChange: onNameChange, value: nameState, placeholder: "", title: props.trans.__('Enter a tag name') }),
React.createElement("p", null, props.trans.__('Create tag pointing to…')),
props.isSingleCommit ? null : (React.createElement("div", { className: filterWrapperClass },
React.createElement("div", { className: filterClass },
React.createElement("input", { className: filterInputClass, type: "text", onChange: onFilterChange, value: filterState, placeholder: props.trans.__('Filter by commit message'), title: props.trans.__('Filter history of commits menu') }),
filterState ? (React.createElement("button", { className: filterClearClass },
React.createElement(ClearIcon, { titleAccess: props.trans.__('Clear the current filter'), fontSize: "small", onClick: resetFilter }))) : null))),
React.createElement(DialogBoxCommitGraph, { pastCommits: props.pastCommits, model: props.model, trans: props.trans, filter: filterState, baseCommitId: baseCommitIdState, updateBaseCommitId: setBaseCommitIdState, isSingleCommit: props.isSingleCommit })),
React.createElement(DialogActions, { className: actionsWrapperClass },
React.createElement("input", { className: classes(buttonClass, cancelButtonClass), type: "button", title: props.trans.__('Close this dialog without creating a new tag'), value: props.trans.__('Cancel'), onClick: onClose }),
React.createElement("input", { className: classes(buttonClass, createButtonClass), type: "button", title: props.trans.__('Create a new tag'), value: props.trans.__('Create Tag'), onClick: () => {
createTag();
}, disabled: nameState === '' || errorState !== '' }))));
};