Skip to content

Commit 55bb992

Browse files
authored
Use preview tab for diff file views (#1184)
* create PreviewMainAreaWidget extension class from MainAreaWidget that allows only one diff file open upon click * fix typo * find/get tab in addDiff cmd * updt tabtitle fontstyle once clicked * . * clean PR and follow suggested advice * clean code * resolve all PR comments * resolve PR comments
1 parent 2e8fd94 commit 55bb992

File tree

3 files changed

+121
-7
lines changed

3 files changed

+121
-7
lines changed

src/commandsAndMenu.tsx

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
1515
import { ITerminal } from '@jupyterlab/terminal';
1616
import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
1717
import { closeIcon, ContextMenuSvg } from '@jupyterlab/ui-components';
18-
import { ArrayExt, toArray } from '@lumino/algorithm';
18+
import { ArrayExt, toArray, find } from '@lumino/algorithm';
1919
import { CommandRegistry } from '@lumino/commands';
2020
import { PromiseDelegate } from '@lumino/coreutils';
2121
import { Message } from '@lumino/messaging';
22-
import { ContextMenu, Menu, Panel, Widget } from '@lumino/widgets';
22+
import { ContextMenu, DockPanel, Menu, Panel, Widget } from '@lumino/widgets';
2323
import * as React from 'react';
2424
import { DiffModel } from './components/diff/model';
2525
import { createPlainTextDiff } from './components/diff/PlainTextDiff';
@@ -50,6 +50,7 @@ import { discardAllChanges } from './widgets/discardAllChanges';
5050
import { ManageRemoteDialogue } from './components/ManageRemoteDialogue';
5151
import { CheckboxForm } from './widgets/GitResetToRemoteForm';
5252
import { AdvancedPushForm } from './widgets/AdvancedPushForm';
53+
import { PreviewMainAreaWidget } from './components/diff/PreviewMainAreaWidget';
5354

5455
export interface IGitCloneArgs {
5556
/**
@@ -87,6 +88,7 @@ interface IFileDiffArgument {
8788
filePath: string;
8889
isText: boolean;
8990
status?: Git.Status;
91+
isPreview?: boolean;
9092

9193
// when file has been relocated
9294
previousFilePath?: string;
@@ -528,9 +530,10 @@ export function addCommands(
528530
label: trans.__('Show Diff'),
529531
caption: trans.__('Display a file diff.'),
530532
execute: async args => {
531-
const { model, isText } = args as any as {
533+
const { model, isText, isPreview } = args as any as {
532534
model: Git.Diff.IModel;
533535
isText?: boolean;
536+
isPreview?: boolean;
534537
};
535538

536539
const fullPath = PathExt.join(
@@ -556,9 +559,10 @@ export function addCommands(
556559
if (!mainAreaItem) {
557560
const content = new Panel();
558561
const modelIsLoading = new PromiseDelegate<void>();
559-
const diffWidget = (mainAreaItem = new MainAreaWidget<Panel>({
562+
const diffWidget = (mainAreaItem = new PreviewMainAreaWidget<Panel>({
560563
content,
561-
reveal: modelIsLoading.promise
564+
reveal: modelIsLoading.promise,
565+
isPreview
562566
}));
563567
diffWidget.id = id;
564568
diffWidget.title.label = PathExt.basename(model.filename);
@@ -571,6 +575,19 @@ export function addCommands(
571575
shell.add(diffWidget, 'main');
572576
shell.activateById(diffWidget.id);
573577

578+
// Search for the tab
579+
const dockPanel = (app.shell as any)._dockPanel as DockPanel;
580+
581+
// Get the index of the most recent tab opened
582+
let tabPosition = -1;
583+
const tabBar = find(dockPanel.tabBars(), bar => {
584+
tabPosition = bar.titles.indexOf(diffWidget.title);
585+
return tabPosition !== -1;
586+
});
587+
588+
// Pin the preview screen if applicable
589+
PreviewMainAreaWidget.pinWidget(tabPosition, tabBar, diffWidget);
590+
574591
// Create the diff widget
575592
try {
576593
const widget = await buildDiffWidget(
@@ -855,7 +872,14 @@ export function addCommands(
855872
execute: async args => {
856873
const { files } = args as any as CommandArguments.IGitFileDiff;
857874
for (const file of files) {
858-
const { context, filePath, previousFilePath, isText, status } = file;
875+
const {
876+
context,
877+
filePath,
878+
previousFilePath,
879+
isText,
880+
status,
881+
isPreview
882+
} = file;
859883

860884
// nothing to compare to for untracked files
861885
if (status === 'untracked') {
@@ -967,7 +991,8 @@ export function addCommands(
967991

968992
const widget = await commands.execute(CommandIDs.gitShowDiff, {
969993
model,
970-
isText
994+
isText,
995+
isPreview
971996
} as any);
972997

973998
if (widget) {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { MainAreaWidget } from '@jupyterlab/apputils/lib/mainareawidget';
2+
import { Message } from '@lumino/messaging';
3+
import { Panel, TabBar, Widget } from '@lumino/widgets';
4+
5+
export class PreviewMainAreaWidget<
6+
T extends Widget = Widget
7+
> extends MainAreaWidget {
8+
/**
9+
* Handle on the preview widget
10+
*/
11+
protected static previewWidget: PreviewMainAreaWidget | null = null;
12+
13+
constructor(options: MainAreaWidget.IOptions<T> & { isPreview?: boolean }) {
14+
super(options);
15+
16+
if (options.isPreview ?? true) {
17+
PreviewMainAreaWidget.disposePreviewWidget(
18+
PreviewMainAreaWidget.previewWidget
19+
);
20+
PreviewMainAreaWidget.previewWidget = this;
21+
}
22+
}
23+
24+
/**
25+
* Dispose screen as a preview screen
26+
*/
27+
static disposePreviewWidget(isPreview: PreviewMainAreaWidget<Widget>): void {
28+
return isPreview && PreviewMainAreaWidget.previewWidget.dispose();
29+
}
30+
31+
/**
32+
* Pin the preview screen if user clicks on tab title
33+
*/
34+
static pinWidget(
35+
tabPosition: number,
36+
tabBar: TabBar<Widget>,
37+
diffWidget: PreviewMainAreaWidget<Panel>
38+
): void {
39+
// We need to wait for the tab node to be inserted in the DOM
40+
setTimeout(() => {
41+
// Get the most recent tab opened
42+
const tab =
43+
tabPosition >= 0 ? tabBar.contentNode.children[tabPosition] : null;
44+
const tabTitle = tab.querySelector<HTMLElement>('.lm-TabBar-tabLabel');
45+
46+
tabTitle.classList.add('jp-git-tab-mod-preview');
47+
48+
const onClick = () => {
49+
tabTitle.classList.remove('jp-git-tab-mod-preview');
50+
tabTitle.removeEventListener('click', onClick, true);
51+
if (PreviewMainAreaWidget.previewWidget === diffWidget) {
52+
PreviewMainAreaWidget.previewWidget = null;
53+
}
54+
};
55+
56+
tabTitle.addEventListener('click', onClick, true);
57+
diffWidget.disposed.connect(() => {
58+
tabTitle.removeEventListener('click', onClick, true);
59+
});
60+
}, 0);
61+
}
62+
63+
/**
64+
* Callback just after the widget is attached to the DOM
65+
*/
66+
protected onAfterAttach(msg: Message): void {
67+
super.onAfterAttach(msg);
68+
this.node.addEventListener('click', this._onClick.bind(this), false);
69+
}
70+
71+
/**
72+
* Callback just before the widget is detached from the DOM
73+
*/
74+
protected onBeforeDetach(msg: Message): void {
75+
this.node.removeEventListener('click', this._onClick.bind(this), false);
76+
super.onBeforeAttach(msg);
77+
}
78+
79+
/**
80+
* Callback on click event in capture phase
81+
*/
82+
_onClick(): void {
83+
PreviewMainAreaWidget.previewWidget = null;
84+
}
85+
}

style/base.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@
1010
@import url('variables.css');
1111
@import url('status-widget.css');
1212
@import url('advanced-push-form.css');
13+
14+
.jp-git-tab-mod-preview {
15+
font-style: italic;
16+
}

0 commit comments

Comments
 (0)