Skip to content

Commit e62d5da

Browse files
committed
Better design for context menus
1 parent a4d5970 commit e62d5da

File tree

5 files changed

+275
-290
lines changed

5 files changed

+275
-290
lines changed

src/commandsAndMenu.ts

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@ import {
77
showErrorMessage
88
} from '@jupyterlab/apputils';
99
import { FileBrowser } from '@jupyterlab/filebrowser';
10+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
1011
import { ISettingRegistry } from '@jupyterlab/settingregistry';
1112
import { ITerminal } from '@jupyterlab/terminal';
1213
import { CommandRegistry } from '@lumino/commands';
1314
import { Menu } from '@lumino/widgets';
14-
import { IGitExtension } from './tokens';
15+
import { Git } from './tokens';
1516
import { GitCredentialsForm } from './widgets/CredentialsBox';
1617
import { doGitClone } from './widgets/gitClone';
1718
import { GitPullPushDialog, Operation } from './widgets/gitPushPull';
19+
import { openListedFile, discardChanges } from './utils';
20+
import { GitExtension } from './model';
21+
import { openDiffView } from './components/diff/DiffWidget';
22+
import { PathExt } from '@jupyterlab/coreutils';
1823

1924
const RESOURCES = [
2025
{
@@ -42,16 +47,27 @@ export namespace CommandIDs {
4247
export const gitOpenGitignore = 'git:open-gitignore';
4348
export const gitPush = 'git:push';
4449
export const gitPull = 'git:pull';
50+
// Context menu commands
51+
export const gitFileDiffIndex = 'git:context-diffIndex';
52+
export const gitFileDiffWorking = 'git:context-diffWorking';
53+
export const gitFileDiscard = 'git:context-discard';
54+
export const gitFileOpen = 'git:context-open';
55+
export const gitFileUnstage = 'git:context-unstage';
56+
export const gitFileStage = 'git:context-stage';
57+
export const gitFileTrack = 'git:context-track';
58+
export const gitIgnore = 'git:context-ignore';
59+
export const gitIgnoreExtension = 'git:context-ignoreExtension';
4560
}
4661

4762
/**
4863
* Add the commands for the git extension.
4964
*/
5065
export function addCommands(
5166
app: JupyterFrontEnd,
52-
model: IGitExtension,
67+
model: GitExtension,
5368
fileBrowser: FileBrowser,
54-
settings: ISettingRegistry.ISettings
69+
settings: ISettingRegistry.ISettings,
70+
renderMime: IRenderMimeRegistry
5571
) {
5672
const { commands, shell } = app;
5773

@@ -232,6 +248,144 @@ export function addCommands(
232248
);
233249
}
234250
});
251+
252+
/* Context menu commands */
253+
commands.addCommand(CommandIDs.gitFileOpen, {
254+
label: 'Open',
255+
caption: 'Open selected file',
256+
execute: async args => {
257+
const selectedFile: Git.IStatusFileResult = args as any;
258+
await openListedFile(selectedFile, model);
259+
},
260+
isEnabled: args => args && args.to !== undefined
261+
});
262+
263+
commands.addCommand(CommandIDs.gitFileDiffWorking, {
264+
label: 'Diff',
265+
caption: 'Diff selected file',
266+
execute: async args => {
267+
const selectedFile: Git.IStatusFileResult = args as any;
268+
await openDiffView(
269+
selectedFile.to,
270+
model,
271+
{
272+
currentRef: { specialRef: 'WORKING' },
273+
previousRef: { gitRef: 'HEAD' }
274+
},
275+
renderMime,
276+
!selectedFile.is_binary
277+
);
278+
},
279+
isEnabled: args => args && args.to !== undefined
280+
});
281+
282+
commands.addCommand(CommandIDs.gitFileDiffIndex, {
283+
label: 'Diff',
284+
caption: 'Diff selected file',
285+
execute: async args => {
286+
const selectedFile: Git.IStatusFileResult = args as any;
287+
await openDiffView(
288+
selectedFile.to,
289+
model,
290+
{
291+
currentRef: { specialRef: 'INDEX' },
292+
previousRef: { gitRef: 'HEAD' }
293+
},
294+
renderMime,
295+
!selectedFile.is_binary
296+
);
297+
},
298+
isEnabled: args => args && args.to !== undefined
299+
});
300+
301+
commands.addCommand(CommandIDs.gitFileStage, {
302+
label: 'Stage',
303+
caption: 'Stage the changes of selected file',
304+
execute: async args => {
305+
const selectedFile: Git.IStatusFile = args as any;
306+
await model.add(selectedFile.to);
307+
},
308+
isEnabled: args => args && args.to !== undefined
309+
});
310+
311+
commands.addCommand(CommandIDs.gitFileTrack, {
312+
label: 'Track',
313+
caption: 'Start tracking selected file',
314+
execute: async args => {
315+
const selectedFile: Git.IStatusFile = args as any;
316+
await model.add(selectedFile.to);
317+
},
318+
isEnabled: args => args && args.to !== undefined
319+
});
320+
321+
commands.addCommand(CommandIDs.gitFileUnstage, {
322+
label: 'Unstage',
323+
caption: 'Unstage the changes of selected file',
324+
execute: async args => {
325+
const selectedFile: Git.IStatusFile = args as any;
326+
if (selectedFile.x !== 'D') {
327+
await model.reset(selectedFile.to);
328+
}
329+
},
330+
isEnabled: args => args && args.to !== undefined
331+
});
332+
333+
commands.addCommand(CommandIDs.gitFileDiscard, {
334+
label: 'Discard',
335+
caption: 'Discard recent changes of selected file',
336+
execute: async args => {
337+
const selectedFile: Git.IStatusFile = args as any;
338+
await discardChanges(selectedFile, model);
339+
},
340+
isEnabled: args => args && args.to !== undefined
341+
});
342+
343+
commands.addCommand(CommandIDs.gitIgnore, {
344+
label: () => 'Ignore this file (add to .gitignore)',
345+
caption: () => 'Ignore this file (add to .gitignore)',
346+
execute: async args => {
347+
const selectedFile: Git.IStatusFile = args as any;
348+
if (selectedFile) {
349+
await model.ignore(selectedFile.to, false);
350+
}
351+
},
352+
isEnabled: args => args && args.to !== undefined
353+
});
354+
355+
commands.addCommand(CommandIDs.gitIgnoreExtension, {
356+
label: args => {
357+
const selectedFile: Git.IStatusFile = args as any;
358+
return `Ignore ${PathExt.extname(
359+
selectedFile.to
360+
)} extension (add to .gitignore)`;
361+
},
362+
caption: 'Ignore this file extension (add to .gitignore)',
363+
execute: async args => {
364+
const selectedFile: Git.IStatusFile = args as any;
365+
if (selectedFile) {
366+
const extension = PathExt.extname(selectedFile.to);
367+
if (extension.length > 0) {
368+
const result = await showDialog({
369+
title: 'Ignore file extension',
370+
body: `Are you sure you want to ignore all ${extension} files within this git repository?`,
371+
buttons: [
372+
Dialog.cancelButton(),
373+
Dialog.okButton({ label: 'Ignore' })
374+
]
375+
});
376+
if (result.button.label === 'Ignore') {
377+
await model.ignore(selectedFile.to, true);
378+
}
379+
}
380+
}
381+
},
382+
isEnabled: args => args && args.to !== undefined,
383+
isVisible: args => {
384+
const selectedFile: Git.IStatusFile = args as any;
385+
const extension = PathExt.extname(selectedFile.to);
386+
return extension.length > 0;
387+
}
388+
});
235389
}
236390

237391
/**
@@ -295,7 +449,7 @@ namespace Private {
295449
* @returns Promise for displaying a dialog
296450
*/
297451
export async function showGitOperationDialog(
298-
model: IGitExtension,
452+
model: GitExtension,
299453
operation: Operation
300454
): Promise<void> {
301455
const title = `Git ${operation}`;

src/components/FileItem.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const STATUS_CODES = {
2727

2828
export interface IFileItemProps {
2929
actions?: React.ReactElement;
30-
contextMenu?: (event: React.MouseEvent) => void;
30+
contextMenu?: (file: Git.IStatusFile, event: React.MouseEvent) => void;
3131
file: Git.IStatusFile;
3232
markBox?: boolean;
3333
model: GitExtension;
@@ -88,10 +88,7 @@ export class FileItem extends React.Component<IFileItemProps> {
8888
onContextMenu={
8989
this.props.contextMenu &&
9090
(event => {
91-
if (this.props.selectFile) {
92-
this.props.selectFile(this.props.file);
93-
}
94-
this.props.contextMenu(event);
91+
this.props.contextMenu(this.props.file, event);
9592
})
9693
}
9794
onDoubleClick={this.props.onDoubleClick}

0 commit comments

Comments
 (0)