Skip to content

Commit 363164d

Browse files
authored
Remove tab (#117)
* Remove Tab * Add test * Add confirm dialog
1 parent b1a98f5 commit 363164d

File tree

9 files changed

+652
-871
lines changed

9 files changed

+652
-871
lines changed

glue_jupyterlab/glue_session.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def render_viewer(self) -> None:
109109
"""Fill the place holder output with glu-jupyter widgets"""
110110

111111
all_tabs = self._document.get_tab_names()
112-
for frontend_tab in self._viewers:
112+
for frontend_tab in list(self._viewers):
113113
if frontend_tab not in all_tabs:
114114
# Tab removed from the frontend
115115
self.remove_tab(frontend_tab)

glue_jupyterlab/glue_ydoc.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,12 @@ def get(self) -> str:
6969

7070
contents["__main__"].setdefault("viewers", [])
7171

72-
while len(contents["__main__"]["viewers"]) != len(tab_names):
73-
contents["__main__"]["viewers"].append([])
74-
75-
viewer_names = []
76-
for idx, tab in enumerate(tab_names):
77-
viewers = tabs[tab]
72+
contents["__main__"]["viewers"] = []
73+
for tab in tab_names:
74+
viewers = tabs.get(tab, {})
7875
viewer_names = sorted(list(viewers.keys()))
7976

80-
contents["__main__"]["viewers"][idx] = viewer_names
77+
contents["__main__"]["viewers"].append(viewer_names)
8178
for viewer in viewer_names:
8279
contents[viewer] = viewers[viewer]
8380

@@ -92,7 +89,6 @@ def get(self) -> str:
9289
contents[data_name] = dataset[data_name]
9390

9491
self.add_links_to_contents(links, contents)
95-
9692
return json.dumps(contents, indent=2, sort_keys=True)
9793

9894
def set(self, value: str) -> None:

glue_jupyterlab/tests/test_ydoc.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ def test_get(session_path, yglue_doc):
4444
assert "NewScatter" in updated_content["__main__"]["viewers"][2]
4545

4646

47+
def test_get_remove_tab(yglue_doc):
48+
## Fake editing of the y structure
49+
with yglue_doc._ydoc.begin_transaction() as t:
50+
# Remove a tab
51+
yglue_doc._ytabs.pop(t, "Tab 1", None)
52+
53+
updated_content = json.loads(yglue_doc.get())
54+
55+
assert "Tab 1" not in updated_content["__main__"]["tab_names"]
56+
assert len(updated_content["__main__"]["viewers"]) == 1
57+
58+
4759
def test_links(yglue_doc_links, identity_link):
4860
yglue_doc_links.get()
4961
links = yglue_doc_links.links

src/document/sharedModel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ export class GlueSessionSharedModel
131131
}, false);
132132
}
133133

134+
removeTab(name: string): void {
135+
if (this._tabs.has(name)) {
136+
this.transact(() => {
137+
this._tabs.delete(name);
138+
}, false);
139+
}
140+
}
141+
134142
getTabNames(): string[] {
135143
return [...this._tabs.keys()];
136144
}

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface IGlueSessionSharedModel
5353
tabsChanged: ISignal<IGlueSessionSharedModel, IDict>;
5454
localStateChanged: ISignal<IGlueSessionSharedModel, { keys: string[] }>;
5555
addTab(): void;
56+
removeTab(tabName: string): void;
5657
getTabNames(): string[];
5758
getTabData(tabName: string): IDict<IGlueSessionViewerTypes> | undefined;
5859

src/viewPanel/sessionWidget.ts

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
1-
import { PromiseDelegate } from '@lumino/coreutils';
2-
import { TabBar, BoxPanel, Widget } from '@lumino/widgets';
3-
41
import { Dialog, InputDialog, showDialog } from '@jupyterlab/apputils';
5-
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
62
import { DocumentRegistry } from '@jupyterlab/docregistry';
73
import { INotebookTracker } from '@jupyterlab/notebook';
4+
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
5+
import { IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel';
6+
import { CommandRegistry } from '@lumino/commands';
7+
import { PromiseDelegate } from '@lumino/coreutils';
8+
import { Message } from '@lumino/messaging';
9+
import { BoxPanel, TabBar, Widget } from '@lumino/widgets';
10+
import { IJupyterYWidgetManager } from 'yjs-widgets';
811

12+
import { CommandIDs } from '../commands';
913
import { HTabPanel } from '../common/tabPanel';
10-
import { DATASET_MIME, IDict, IGlueSessionSharedModel } from '../types';
1114
import { GlueSessionModel } from '../document/docModel';
15+
import { LinkEditor } from '../linkPanel/linkEditor';
1216
import { mockNotebook } from '../tools';
17+
import { DATASET_MIME, IDict, IGlueSessionSharedModel } from '../types';
1318
import { TabView } from './tabView';
14-
import { LinkEditor } from '../linkPanel/linkEditor';
15-
16-
import { Message } from '@lumino/messaging';
17-
import { CommandRegistry } from '@lumino/commands';
18-
import { CommandIDs } from '../commands';
19-
import { IJupyterYWidgetManager } from 'yjs-widgets';
20-
import { IKernelConnection } from '@jupyterlab/services/lib/kernel/kernel';
2119

2220
export class SessionWidget extends BoxPanel {
2321
constructor(options: SessionWidget.IOptions) {
@@ -50,6 +48,18 @@ export class SessionWidget extends BoxPanel {
5048
this._tabPanel.topBar.addRequested.connect(() => {
5149
this._model.addTab();
5250
});
51+
this._tabPanel.topBar.tabCloseRequested.connect(async (tab, arg) => {
52+
const confirm = await showDialog({
53+
title: 'Delete Tab',
54+
body: 'Are you sure you want to delete this tab?',
55+
buttons: [Dialog.cancelButton(), Dialog.okButton({ label: 'Delete' })]
56+
});
57+
if (confirm.button.accept) {
58+
arg.title.owner.close();
59+
this._tabPanel.topBar.removeTabAt(arg.index);
60+
this._model.removeTab(arg.title.label);
61+
}
62+
});
5363
if (this._model) {
5464
this._linkWidget = new LinkEditor({ sharedModel: this._model });
5565
this._tabPanel.addTab(this._linkWidget, 0);
@@ -166,7 +176,12 @@ export class SessionWidget extends BoxPanel {
166176
let newTabIndex: number | undefined = undefined;
167177
const currentIndex = this._tabPanel.topBar.currentIndex;
168178
const tabNames = this._model.getTabNames();
169-
179+
Object.keys(this._tabViews).forEach(k => {
180+
if (!tabNames.includes(k)) {
181+
this._tabViews[k].dispose();
182+
delete this._tabViews[k];
183+
}
184+
});
170185
tabNames.forEach((tabName, idx) => {
171186
// Tab already exists, we don't do anything
172187
if (tabName in this._tabViews) {
@@ -182,7 +197,7 @@ export class SessionWidget extends BoxPanel {
182197
notebookTracker: this._notebookTracker,
183198
commands: this._commands
184199
}));
185-
200+
tabWidget.title.closable = true;
186201
this._tabPanel.addTab(tabWidget, idx + 1);
187202
});
188203

src/viewPanel/tabLayout.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ export class TabLayout extends Layout {
412412
private _grid: GridStack;
413413
private _gridItems: Map<string, GridStackItem>;
414414
private _gridItemChanged = new Signal<this, TabLayout.IChange>(this);
415-
private _resizeTimeout = 0;
415+
private _resizeTimeout: any = 0;
416416
private _commands: CommandRegistry;
417417
}
418418

src/viewPanel/tabView.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export class TabView extends Widget {
5454
super.dispose();
5555
}
5656

57+
protected onCloseRequest(msg: Message): void {
58+
this.dispose();
59+
}
60+
5761
/**
5862
* The tab name
5963
*/

0 commit comments

Comments
 (0)