Skip to content

Commit 924be51

Browse files
authored
Add context menu on commits in the HistorySidebar with add tag command (#1277)
* add context menu on commits in the HistorySidebar with add tag command * remove additional comments * hide filter div when adding tag from context menu * add integration test for adding a tag from context menu
1 parent 507fdb3 commit 924be51

File tree

7 files changed

+248
-24
lines changed

7 files changed

+248
-24
lines changed

src/commandsAndMenu.tsx

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ContextMenu, DockPanel, Menu, Panel, Widget } from '@lumino/widgets';
2424
import * as React from 'react';
2525
import { CancelledError } from './cancelledError';
2626
import { BranchPicker } from './components/BranchPicker';
27+
import { NewTagDialogBox } from './components/NewTagDialog';
2728
import { DiffModel } from './components/diff/model';
2829
import { createPlainTextDiff } from './components/diff/PlainTextDiff';
2930
import { PreviewMainAreaWidget } from './components/diff/PreviewMainAreaWidget';
@@ -39,7 +40,8 @@ import {
3940
gitIcon,
4041
historyIcon,
4142
openIcon,
42-
removeIcon
43+
removeIcon,
44+
tagIcon
4345
} from './style/icons';
4446
import {
4547
CommandIDs,
@@ -102,6 +104,9 @@ export namespace CommandArguments {
102104
export interface IGitContextAction {
103105
files: Git.IStatusFile[];
104106
}
107+
export interface IGitCommitInfo {
108+
commit: Git.ISingleCommitInfo;
109+
}
105110
}
106111

107112
function pluralizedContextLabel(singular: string, plural: string) {
@@ -1537,6 +1542,79 @@ export function addCommands(
15371542
isEnabled: () => false,
15381543
execute: () => void 0
15391544
});
1545+
1546+
commands.addCommand(ContextCommandIDs.gitTagAdd, {
1547+
label: trans.__('Add Tag'),
1548+
caption: trans.__('Add tag pointing to selected commit'),
1549+
execute: async args => {
1550+
const commit = args as any as CommandArguments.IGitCommitInfo;
1551+
1552+
const widgetId = 'git-dialog-AddTag';
1553+
let anchor = document.querySelector<HTMLDivElement>(`#${widgetId}`);
1554+
if (!anchor) {
1555+
anchor = document.createElement('div');
1556+
anchor.id = widgetId;
1557+
document.body.appendChild(anchor);
1558+
}
1559+
1560+
const tagDialog = true;
1561+
const isSingleCommit = true;
1562+
1563+
const waitForDialog = new PromiseDelegate<string | null>();
1564+
const dialog = ReactWidget.create(
1565+
<NewTagDialogBox
1566+
pastCommits={[commit.commit]}
1567+
logger={logger}
1568+
model={gitModel}
1569+
trans={trans}
1570+
open={tagDialog}
1571+
onClose={(tagName?: string) => {
1572+
dialog.dispose();
1573+
waitForDialog.resolve(tagName ?? null);
1574+
}}
1575+
isSingleCommit={isSingleCommit}
1576+
/>
1577+
);
1578+
1579+
Widget.attach(dialog, anchor);
1580+
1581+
const tagName = await waitForDialog.promise;
1582+
1583+
if (tagName) {
1584+
logger.log({
1585+
level: Level.RUNNING,
1586+
message: trans.__(
1587+
"Create tag pointing to '%1'...",
1588+
commit.commit.commit_msg
1589+
)
1590+
});
1591+
try {
1592+
await gitModel.setTag(tagName, commit.commit.commit);
1593+
} catch (err) {
1594+
logger.log({
1595+
level: Level.ERROR,
1596+
message: trans.__(
1597+
"Failed to create tag '%1' poining to '%2'.",
1598+
tagName,
1599+
commit
1600+
),
1601+
error: err as Error
1602+
});
1603+
return;
1604+
}
1605+
1606+
logger.log({
1607+
level: Level.SUCCESS,
1608+
message: trans.__(
1609+
"Created tag '%1' pointing to '%2'.",
1610+
tagName,
1611+
commit
1612+
)
1613+
});
1614+
}
1615+
},
1616+
icon: tagIcon.bindprops({ stylesheet: 'menuItem' })
1617+
});
15401618
}
15411619

15421620
/**
@@ -1639,6 +1717,21 @@ export function addMenuItems(
16391717
});
16401718
}
16411719

1720+
export function addHistoryMenuItems(
1721+
commands: ContextCommandIDs[],
1722+
contextMenu: Menu,
1723+
selectedCommit: Git.ISingleCommitInfo
1724+
): void {
1725+
commands.forEach(command => {
1726+
contextMenu.addItem({
1727+
command,
1728+
args: {
1729+
commit: selectedCommit
1730+
} as CommandArguments.IGitCommitInfo as any
1731+
});
1732+
});
1733+
}
1734+
16421735
/**
16431736
* Populate Git context submenu depending on the selected files.
16441737
*/

src/components/HistorySideBar.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { TranslationBundle } from '@jupyterlab/translation';
22
import { closeIcon } from '@jupyterlab/ui-components';
33
import { CommandRegistry } from '@lumino/commands';
4+
import { Menu } from '@lumino/widgets';
5+
import { addHistoryMenuItems } from '../commandsAndMenu';
46
import * as React from 'react';
57
import { GitExtension } from '../model';
68
import { hiddenButtonStyle } from '../style/ActionButtonStyle';
@@ -10,7 +12,7 @@ import {
1012
selectedHistoryFileStyle,
1113
historySideBarWrapperStyle
1214
} from '../style/HistorySideBarStyle';
13-
import { Git } from '../tokens';
15+
import { ContextCommandIDs, Git } from '../tokens';
1416
import { openFileDiff } from '../utils';
1517
import { ActionButton } from './ActionButton';
1618
import { FileItem } from './FileItem';
@@ -79,6 +81,8 @@ export interface IHistorySideBarProps {
7981
) => (event: React.MouseEvent<HTMLElement, MouseEvent>) => Promise<void>;
8082
}
8183

84+
export const CONTEXT_COMMANDS = [ContextCommandIDs.gitTagAdd];
85+
8286
/**
8387
* Returns a React component for displaying commit history.
8488
*
@@ -145,6 +149,25 @@ export const HistorySideBar: React.FunctionComponent<IHistorySideBarProps> = (
145149
return () => resizeObserver.disconnect();
146150
}, [props.commits]);
147151

152+
/**
153+
* Open the context menu on the advanced view
154+
*
155+
* @param selectedCommit The commit on which the context menu is opened
156+
* @param event The click event
157+
*/
158+
const openContextMenu = (
159+
selectedCommit: Git.ISingleCommitInfo,
160+
event: React.MouseEvent
161+
): void => {
162+
event.preventDefault();
163+
164+
const contextMenu = new Menu({ commands: props.commands });
165+
const commands = [ContextCommandIDs.gitTagAdd];
166+
addHistoryMenuItems(commands, contextMenu, selectedCommit);
167+
168+
contextMenu.open(event.clientX, event.clientY);
169+
};
170+
148171
return (
149172
<div className={historySideBarWrapperStyle}>
150173
{!props.model.selectedHistoryFile && (
@@ -232,6 +255,7 @@ export const HistorySideBar: React.FunctionComponent<IHistorySideBarProps> = (
232255
}
233256
})
234257
}
258+
contextMenu={openContextMenu}
235259
>
236260
{!props.model.selectedHistoryFile && (
237261
<SinglePastCommitInfo

src/components/NewTagDialog.tsx

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export interface INewTagDialogProps {
4646
pastCommits: Git.ISingleCommitInfo[];
4747

4848
/**
49-
* Extension logger
49+
* Extension logger.
5050
*/
5151
logger: Logger;
5252

@@ -69,6 +69,11 @@ export interface INewTagDialogProps {
6969
* The application language translator.
7070
*/
7171
trans: TranslationBundle;
72+
73+
/**
74+
* Dialog box open from the context menu?
75+
*/
76+
isSingleCommit: boolean;
7277
}
7378

7479
/**
@@ -109,6 +114,11 @@ export interface IDialogBoxCommitGraphProps {
109114
* Update function for baseCommitId.
110115
*/
111116
updateBaseCommitId: React.Dispatch<React.SetStateAction<string>>;
117+
118+
/**
119+
* Dialog box open from the context menu.
120+
*/
121+
isSingleCommit: boolean;
112122
}
113123

114124
/**
@@ -185,6 +195,10 @@ export const DialogBoxCommitGraph: React.FunctionComponent<
185195
isFilter = true;
186196
}
187197

198+
if (props.isSingleCommit === true) {
199+
isFilter = false;
200+
}
201+
188202
return (
189203
<div className={historyDialogBoxWrapperStyle}>
190204
{isFilter && (
@@ -427,27 +441,29 @@ export const NewTagDialogBox: React.FunctionComponent<INewTagDialogProps> = (
427441
title={props.trans.__('Enter a tag name')}
428442
/>
429443
<p>{props.trans.__('Create tag pointing to…')}</p>
430-
<div className={filterWrapperClass}>
431-
<div className={filterClass}>
432-
<input
433-
className={filterInputClass}
434-
type="text"
435-
onChange={onFilterChange}
436-
value={filterState}
437-
placeholder={props.trans.__('Filter by commit message')}
438-
title={props.trans.__('Filter history of commits menu')}
439-
/>
440-
{filterState ? (
441-
<button className={filterClearClass}>
442-
<ClearIcon
443-
titleAccess={props.trans.__('Clear the current filter')}
444-
fontSize="small"
445-
onClick={resetFilter}
446-
/>
447-
</button>
448-
) : null}
444+
{props.isSingleCommit ? null : (
445+
<div className={filterWrapperClass}>
446+
<div className={filterClass}>
447+
<input
448+
className={filterInputClass}
449+
type="text"
450+
onChange={onFilterChange}
451+
value={filterState}
452+
placeholder={props.trans.__('Filter by commit message')}
453+
title={props.trans.__('Filter history of commits menu')}
454+
/>
455+
{filterState ? (
456+
<button className={filterClearClass}>
457+
<ClearIcon
458+
titleAccess={props.trans.__('Clear the current filter')}
459+
fontSize="small"
460+
onClick={resetFilter}
461+
/>
462+
</button>
463+
) : null}
464+
</div>
449465
</div>
450-
</div>
466+
)}
451467
{
452468
<DialogBoxCommitGraph
453469
pastCommits={props.pastCommits}
@@ -457,6 +473,7 @@ export const NewTagDialogBox: React.FunctionComponent<INewTagDialogProps> = (
457473
filter={filterState}
458474
baseCommitId={baseCommitIdState}
459475
updateBaseCommitId={setBaseCommitIdState}
476+
isSingleCommit={props.isSingleCommit}
460477
/>
461478
}
462479
</div>

src/components/PastCommitNode.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,14 @@ export interface IPastCommitNodeProps {
118118
* @param el the <li> element representing a past commit
119119
*/
120120
setRef: (el: HTMLLIElement) => void;
121+
122+
/**
123+
* Callback to open a context menu on the commit
124+
*/
125+
contextMenu?: (
126+
commit: Git.ISingleCommitInfo,
127+
event: React.MouseEvent
128+
) => void;
121129
}
122130

123131
/**
@@ -162,6 +170,12 @@ export class PastCommitNode extends React.Component<
162170
: this.props.trans.__('View file changes')
163171
}
164172
onClick={event => this._onCommitClick(event, this.props.commit.commit)}
173+
onContextMenu={
174+
this.props.contextMenu &&
175+
(event => {
176+
this.props.contextMenu(this.props.commit, event);
177+
})
178+
}
165179
>
166180
<div className={commitHeaderClass}>
167181
<span className={commitHeaderItemClass}>

src/components/TagMenu.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ export class TagMenu extends React.Component<ITagMenuProps, ITagMenuState> {
265265
* @returns React element
266266
*/
267267
private _renderNewTagDialog(): React.ReactElement {
268+
const isSingleCommit = false;
268269
return (
269270
<NewTagDialogBox
270271
pastCommits={this.props.pastCommits}
@@ -273,6 +274,7 @@ export class TagMenu extends React.Component<ITagMenuProps, ITagMenuState> {
273274
trans={this.props.trans}
274275
open={this.state.tagDialog}
275276
onClose={this._onNewTagDialogClose}
277+
isSingleCommit={isSingleCommit}
276278
/>
277279
);
278280
}

src/tokens.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1373,7 +1373,8 @@ export enum ContextCommandIDs {
13731373
gitIgnoreExtension = 'git:context-ignoreExtension',
13741374
gitNoAction = 'git:no-action',
13751375
openFileFromDiff = 'git:open-file-from-diff',
1376-
gitFileStashPop = 'git:context-stash-pop'
1376+
gitFileStashPop = 'git:context-stash-pop',
1377+
gitTagAdd = 'git:context-tag-add'
13771378
}
13781379

13791380
/**

0 commit comments

Comments
 (0)