@jupyterlab/running 4.2.0-alpha.0 → 4.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -6,15 +6,21 @@
6
6
  */
7
7
  import { Dialog, showDialog } from '@jupyterlab/apputils';
8
8
  import { nullTranslator } from '@jupyterlab/translation';
9
- import { caretDownIcon, caretRightIcon, closeIcon, PanelWithToolbar, ReactWidget, refreshIcon, SidePanel, ToolbarButton, ToolbarButtonComponent, UseSignal } from '@jupyterlab/ui-components';
9
+ import { caretDownIcon, caretRightIcon, closeIcon, collapseAllIcon, expandAllIcon, FilterBox, PanelWithToolbar, ReactWidget, refreshIcon, SidePanel, tableRowsIcon, ToolbarButton, ToolbarButtonComponent, treeViewIcon, UseSignal } from '@jupyterlab/ui-components';
10
10
  import { Token } from '@lumino/coreutils';
11
11
  import { DisposableDelegate } from '@lumino/disposable';
12
+ import { ElementExt } from '@lumino/domutils';
12
13
  import { Signal } from '@lumino/signaling';
13
- import * as React from 'react';
14
+ import { Panel, Widget } from '@lumino/widgets';
15
+ import React, { isValidElement } from 'react';
14
16
  /**
15
17
  * The class name added to a running widget.
16
18
  */
17
19
  const RUNNING_CLASS = 'jp-RunningSessions';
20
+ /**
21
+ * The class name added to a searchable widget.
22
+ */
23
+ const SEARCHABLE_CLASS = 'jp-SearchableSessions';
18
24
  /**
19
25
  * The class name added to the running terminal sessions section.
20
26
  */
@@ -48,9 +54,37 @@ const SHUTDOWN_BUTTON_CLASS = 'jp-RunningSessions-itemShutdown';
48
54
  */
49
55
  const SHUTDOWN_ALL_BUTTON_CLASS = 'jp-RunningSessions-shutdownAll';
50
56
  /**
51
- * The running sessions token.
57
+ * The class name added to a collapse/expand carets.
58
+ */
59
+ const CARET_CLASS = 'jp-RunningSessions-caret';
60
+ /**
61
+ * The class name added to icons.
62
+ */
63
+ const ITEM_ICON_CLASS = 'jp-RunningSessions-icon';
64
+ /**
65
+ * Modifier added to a section when flattened list view is requested.
66
+ */
67
+ const LIST_VIEW_CLASS = 'jp-mod-running-list-view';
68
+ /**
69
+ * The class name added to button switching between nested and flat view.
70
+ */
71
+ const VIEW_BUTTON_CLASS = 'jp-RunningSessions-viewButton';
72
+ /**
73
+ * The class name added to button switching between nested and flat view.
74
+ */
75
+ const COLLAPSE_EXPAND_BUTTON_CLASS = 'jp-RunningSessions-collapseButton';
76
+ /**
77
+ * Identifier used in the state database.
78
+ */
79
+ const STATE_DB_ID = 'jp-running-sessions';
80
+ /**
81
+ * The running sessions managers token.
52
82
  */
53
83
  export const IRunningSessionManagers = new Token('@jupyterlab/running:IRunningSessionManagers', 'A service to add running session managers.');
84
+ /**
85
+ * The running sessions token.
86
+ */
87
+ export const IRunningSessionSidebar = new Token('@jupyterlab/running:IRunningSessionsSidebar', 'A token allowing to modify the running sessions sidebar.');
54
88
  export class RunningSessionManagers {
55
89
  constructor() {
56
90
  this._added = new Signal(this);
@@ -97,37 +131,112 @@ function Item(props) {
97
131
  // Handle shutdown requests.
98
132
  let stopPropagation = false;
99
133
  const shutdownItemIcon = props.shutdownItemIcon || closeIcon;
100
- const shutdownLabel = props.shutdownLabel || trans.__('Shut Down');
134
+ const shutdownLabel = (_b = (typeof props.shutdownLabel === 'function'
135
+ ? props.shutdownLabel(runningItem)
136
+ : props.shutdownLabel)) !== null && _b !== void 0 ? _b : trans.__('Shut Down');
101
137
  const shutdown = () => {
102
138
  var _a;
103
139
  stopPropagation = true;
104
140
  (_a = runningItem.shutdown) === null || _a === void 0 ? void 0 : _a.call(runningItem);
105
141
  };
142
+ // Materialise getter to avoid triggering it repeatedly
143
+ const children = runningItem.children;
106
144
  // Manage collapsed state. Use the shutdown flag in lieu of `stopPropagation`.
107
145
  const [collapsed, collapse] = React.useState(false);
108
- const collapsible = !!((_b = runningItem.children) === null || _b === void 0 ? void 0 : _b.length);
146
+ const collapsible = !!(children === null || children === void 0 ? void 0 : children.length);
109
147
  const onClick = collapsible
110
148
  ? () => !stopPropagation && collapse(!collapsed)
111
149
  : undefined;
150
+ // Listen to signal to collapse from outside
151
+ props.collapseToggled.connect((_emitter, newCollapseState) => collapse(newCollapseState));
112
152
  if (runningItem.className) {
113
153
  classList.push(runningItem.className);
114
154
  }
115
155
  if (props.child) {
116
156
  classList.push('jp-mod-running-child');
117
157
  }
158
+ if (props.child && !children) {
159
+ classList.push('jp-mod-running-leaf');
160
+ }
118
161
  return (React.createElement(React.Fragment, null,
119
162
  React.createElement("li", null,
120
163
  React.createElement("div", { className: classList.join(' '), onClick: onClick, "data-context": runningItem.context || '' },
121
164
  collapsible &&
122
- (collapsed ? (React.createElement(caretRightIcon.react, { tag: "span", stylesheet: "runningItem" })) : (React.createElement(caretDownIcon.react, { tag: "span", stylesheet: "runningItem" }))),
123
- icon ? (typeof icon === 'string' ? (React.createElement("img", { src: icon })) : (React.createElement(icon.react, { tag: "span", stylesheet: "runningItem" }))) : undefined,
165
+ (collapsed ? (React.createElement(caretRightIcon.react, { tag: "span", className: CARET_CLASS })) : (React.createElement(caretDownIcon.react, { tag: "span", className: CARET_CLASS }))),
166
+ icon ? (typeof icon === 'string' ? (React.createElement("img", { src: icon, className: ITEM_ICON_CLASS })) : (React.createElement(icon.react, { tag: "span", className: ITEM_ICON_CLASS }))) : undefined,
124
167
  React.createElement("span", { className: ITEM_LABEL_CLASS, title: title, onClick: runningItem.open && (() => runningItem.open()) }, runningItem.label()),
125
168
  detail && React.createElement("span", { className: ITEM_DETAIL_CLASS }, detail),
126
169
  runningItem.shutdown && (React.createElement(ToolbarButtonComponent, { className: SHUTDOWN_BUTTON_CLASS, icon: shutdownItemIcon, onClick: shutdown, tooltip: shutdownLabel }))),
127
- collapsible && !collapsed && (React.createElement(List, { child: true, runningItems: runningItem.children, shutdownItemIcon: shutdownItemIcon, translator: translator })))));
170
+ collapsible && !collapsed && (React.createElement(List, { child: true, runningItems: children, shutdownItemIcon: shutdownItemIcon, translator: translator, collapseToggled: props.collapseToggled })))));
128
171
  }
129
172
  function List(props) {
130
- return (React.createElement("ul", { className: LIST_CLASS }, props.runningItems.map((item, i) => (React.createElement(Item, { child: props.child, key: i, runningItem: item, shutdownLabel: props.shutdownLabel, shutdownItemIcon: props.shutdownItemIcon, translator: props.translator })))));
173
+ const filter = props.filter;
174
+ const items = filter
175
+ ? props.runningItems
176
+ .map(item => {
177
+ return {
178
+ item,
179
+ score: filter(item)
180
+ };
181
+ })
182
+ .filter(({ score }) => score !== null)
183
+ .sort((a, b) => {
184
+ return a.score.score - b.score.score;
185
+ })
186
+ .map(({ item }) => item)
187
+ : props.runningItems;
188
+ return (React.createElement("ul", { className: LIST_CLASS }, items.map((item, i) => (React.createElement(Item, { child: props.child, key: i, runningItem: item, shutdownLabel: props.shutdownLabel, shutdownItemIcon: props.shutdownItemIcon, translator: props.translator, collapseToggled: props.collapseToggled })))));
189
+ }
190
+ class FilterWidget extends ReactWidget {
191
+ constructor(translator) {
192
+ super();
193
+ this._filterFn = (_) => {
194
+ return { score: 0 };
195
+ };
196
+ this._filterChanged = new Signal(this);
197
+ this.filter = this.filter.bind(this);
198
+ this._updateFilter = this._updateFilter.bind(this);
199
+ this._trans = translator.load('jupyterlab');
200
+ this.addClass('jp-SearchableSessions-filter');
201
+ }
202
+ get filterChanged() {
203
+ return this._filterChanged;
204
+ }
205
+ render() {
206
+ return (React.createElement(FilterBox, { placeholder: this._trans.__('Search'), updateFilter: this._updateFilter, useFuzzyFilter: false, caseSensitive: false }));
207
+ }
208
+ filter(item) {
209
+ var _a;
210
+ const labels = [this._getTextContent(item.label())];
211
+ for (const child of (_a = item.children) !== null && _a !== void 0 ? _a : []) {
212
+ labels.push(this._getTextContent(child.label()));
213
+ }
214
+ return this._filterFn(labels.join(' '));
215
+ }
216
+ _getTextContent(node) {
217
+ if (typeof node === 'string') {
218
+ return node;
219
+ }
220
+ if (typeof node === 'number') {
221
+ return '' + node;
222
+ }
223
+ if (typeof node === 'boolean') {
224
+ return '' + node;
225
+ }
226
+ if (Array.isArray(node)) {
227
+ return node.map(n => this._getTextContent(n)).join(' ');
228
+ }
229
+ if (node && isValidElement(node)) {
230
+ return node.props.children
231
+ .map((n) => this._getTextContent(n))
232
+ .join(' ');
233
+ }
234
+ return '';
235
+ }
236
+ _updateFilter(filterFn) {
237
+ this._filterFn = filterFn;
238
+ this._filterChanged.emit();
239
+ }
131
240
  }
132
241
  class ListWidget extends ReactWidget {
133
242
  constructor(_options) {
@@ -135,9 +244,12 @@ class ListWidget extends ReactWidget {
135
244
  this._options = _options;
136
245
  this._update = new Signal(this);
137
246
  _options.manager.runningChanged.connect(this._emitUpdate, this);
247
+ if (_options.filterProvider) {
248
+ _options.filterProvider.filterChanged.connect(this._emitUpdate, this);
249
+ }
138
250
  }
139
251
  dispose() {
140
- this._options.manager.runningChanged.disconnect(this._emitUpdate, this);
252
+ Signal.clearData(this);
141
253
  super.dispose();
142
254
  }
143
255
  onBeforeShow(msg) {
@@ -148,7 +260,8 @@ class ListWidget extends ReactWidget {
148
260
  const options = this._options;
149
261
  let cached = true;
150
262
  return (React.createElement(UseSignal, { signal: this._update }, () => {
151
- // Cache the running items for the intial load and request from
263
+ var _a;
264
+ // Cache the running items for the initial load and request from
152
265
  // the service every subsequent load.
153
266
  if (cached) {
154
267
  cached = false;
@@ -157,7 +270,7 @@ class ListWidget extends ReactWidget {
157
270
  options.runningItems = options.manager.running();
158
271
  }
159
272
  return (React.createElement("div", { className: CONTAINER_CLASS },
160
- React.createElement(List, { runningItems: options.runningItems, shutdownLabel: options.manager.shutdownLabel, shutdownAllLabel: options.shutdownAllLabel, shutdownItemIcon: options.manager.shutdownItemIcon, translator: options.translator })));
273
+ React.createElement(List, { runningItems: options.runningItems, shutdownLabel: options.manager.shutdownLabel, shutdownAllLabel: options.shutdownAllLabel, shutdownItemIcon: options.manager.shutdownItemIcon, filter: (_a = options.filterProvider) === null || _a === void 0 ? void 0 : _a.filter, translator: options.translator, collapseToggled: options.collapseToggled })));
161
274
  }));
162
275
  }
163
276
  /**
@@ -198,15 +311,66 @@ class ListWidget extends ReactWidget {
198
311
  class Section extends PanelWithToolbar {
199
312
  constructor(options) {
200
313
  super();
314
+ this._buttons = null;
315
+ this._listView = false;
316
+ this._collapseToggled = new Signal(this);
317
+ this._viewChanged = new Signal(this);
201
318
  this._manager = options.manager;
319
+ this._filterProvider = options.filterProvider;
202
320
  const translator = options.translator || nullTranslator;
203
- const trans = translator.load('jupyterlab');
204
- const shutdownAllLabel = options.manager.shutdownAllLabel || trans.__('Shut Down All');
205
- const shutdownTitle = `${shutdownAllLabel}?`;
206
- const shutdownAllConfirmationText = options.manager.shutdownAllConfirmationText ||
207
- `${shutdownAllLabel} ${options.manager.name}`;
321
+ this._trans = translator.load('jupyterlab');
208
322
  this.addClass(SECTION_CLASS);
209
323
  this.title.label = options.manager.name;
324
+ this._manager.runningChanged.connect(this._onListChanged, this);
325
+ if (options.filterProvider) {
326
+ options.filterProvider.filterChanged.connect(this._onListChanged, this);
327
+ }
328
+ this._updateEmptyClass();
329
+ let runningItems = options.manager.running();
330
+ if (options.showToolbar !== false) {
331
+ this._initializeToolbar(runningItems);
332
+ }
333
+ this.addWidget(new ListWidget({
334
+ runningItems,
335
+ shutdownAllLabel: this._shutdownAllLabel,
336
+ collapseToggled: this._collapseToggled,
337
+ ...options
338
+ }));
339
+ }
340
+ /**
341
+ * Toggle between list and tree view.
342
+ */
343
+ toggleListView(forceOn) {
344
+ const newState = typeof forceOn !== 'undefined' ? forceOn : !this._listView;
345
+ this._listView = newState;
346
+ if (this._buttons) {
347
+ const switchViewButton = this._buttons['switch-view'];
348
+ switchViewButton.pressed = newState;
349
+ }
350
+ this._collapseToggled.emit(false);
351
+ this.toggleClass(LIST_VIEW_CLASS, newState);
352
+ this._updateButtons();
353
+ this._viewChanged.emit({ mode: newState ? 'list' : 'tree' });
354
+ }
355
+ /**
356
+ * Dispose the resources held by the widget
357
+ */
358
+ dispose() {
359
+ if (this.isDisposed) {
360
+ return;
361
+ }
362
+ Signal.clearData(this);
363
+ super.dispose();
364
+ }
365
+ get _shutdownAllLabel() {
366
+ return this._manager.shutdownAllLabel || this._trans.__('Shut Down All');
367
+ }
368
+ _initializeToolbar(runningItems) {
369
+ const enabled = runningItems.length > 0;
370
+ const shutdownAllLabel = this._shutdownAllLabel;
371
+ const shutdownTitle = `${shutdownAllLabel}?`;
372
+ const shutdownAllConfirmationText = this._manager.shutdownAllConfirmationText ||
373
+ `${shutdownAllLabel} ${this._manager.name}`;
210
374
  function onShutdown() {
211
375
  void showDialog({
212
376
  title: shutdownTitle,
@@ -217,43 +381,87 @@ class Section extends PanelWithToolbar {
217
381
  ]
218
382
  }).then(result => {
219
383
  if (result.button.accept) {
220
- options.manager.shutdownAll();
384
+ this._manager.shutdownAll();
221
385
  }
222
386
  });
223
387
  }
224
- let runningItems = options.manager.running();
225
- const enabled = runningItems.length > 0;
226
- this._button = new ToolbarButton({
388
+ const shutdownAllButton = new ToolbarButton({
227
389
  label: shutdownAllLabel,
228
390
  className: `${SHUTDOWN_ALL_BUTTON_CLASS}${!enabled ? ' jp-mod-disabled' : ''}`,
229
391
  enabled,
230
392
  onClick: onShutdown
231
393
  });
232
- this._manager.runningChanged.connect(this._updateButton, this);
233
- this.toolbar.addItem('shutdown-all', this._button);
234
- this.addWidget(new ListWidget({ runningItems, shutdownAllLabel, ...options }));
235
- }
236
- /**
237
- * Dispose the resources held by the widget
238
- */
239
- dispose() {
240
- if (this.isDisposed) {
241
- return;
394
+ const switchViewButton = new ToolbarButton({
395
+ className: VIEW_BUTTON_CLASS,
396
+ enabled,
397
+ icon: tableRowsIcon,
398
+ pressedIcon: treeViewIcon,
399
+ onClick: () => this.toggleListView(),
400
+ tooltip: this._trans.__('Switch to List View'),
401
+ pressedTooltip: this._trans.__('Switch to Tree View')
402
+ });
403
+ const collapseExpandAllButton = new ToolbarButton({
404
+ className: COLLAPSE_EXPAND_BUTTON_CLASS,
405
+ enabled,
406
+ icon: collapseAllIcon,
407
+ pressedIcon: expandAllIcon,
408
+ onClick: () => {
409
+ const newState = !collapseExpandAllButton.pressed;
410
+ this._collapseToggled.emit(newState);
411
+ collapseExpandAllButton.pressed = newState;
412
+ },
413
+ tooltip: this._trans.__('Collapse All'),
414
+ pressedTooltip: this._trans.__('Expand All')
415
+ });
416
+ this._buttons = {
417
+ 'switch-view': switchViewButton,
418
+ 'collapse-expand': collapseExpandAllButton,
419
+ 'shutdown-all': shutdownAllButton
420
+ };
421
+ // Update buttons once defined and before adding to DOM
422
+ this._updateButtons();
423
+ this._manager.runningChanged.connect(this._updateButtons, this);
424
+ for (const name of ['collapse-expand', 'switch-view', 'shutdown-all']) {
425
+ this.toolbar.addItem(name, this._buttons[name]);
242
426
  }
243
- this._manager.runningChanged.disconnect(this._updateButton, this);
244
- super.dispose();
427
+ this.toolbar.addClass('jp-RunningSessions-toolbar');
428
+ }
429
+ _onListChanged() {
430
+ this._updateButtons();
431
+ this._updateEmptyClass();
245
432
  }
246
- _updateButton() {
247
- var _a, _b;
248
- const button = this._button;
249
- button.enabled = this._manager.running().length > 0;
250
- if (button.enabled) {
251
- (_a = button.node
252
- .querySelector('jp-button')) === null || _a === void 0 ? void 0 : _a.classList.remove('jp-mod-disabled');
433
+ _updateEmptyClass() {
434
+ if (this._filterProvider) {
435
+ const items = this._manager.running().filter(this._filterProvider.filter);
436
+ const empty = items.length === 0;
437
+ if (empty) {
438
+ this.node.classList.toggle('jp-mod-empty', true);
439
+ }
440
+ else {
441
+ this.node.classList.toggle('jp-mod-empty', false);
442
+ }
253
443
  }
254
- else {
255
- (_b = button.node.querySelector('jp-button')) === null || _b === void 0 ? void 0 : _b.classList.add('jp-mod-disabled');
444
+ }
445
+ get viewChanged() {
446
+ return this._viewChanged;
447
+ }
448
+ _updateButtons() {
449
+ if (!this._buttons) {
450
+ return;
256
451
  }
452
+ let runningItems = this._manager.running();
453
+ const enabled = runningItems.length > 0;
454
+ const hasNesting = runningItems.filter(item => item.children).length !== 0;
455
+ const inTreeView = hasNesting && !this._buttons['switch-view'].pressed;
456
+ this._buttons['switch-view'].node.style.display = hasNesting
457
+ ? 'block'
458
+ : 'none';
459
+ this._buttons['collapse-expand'].node.style.display = inTreeView
460
+ ? 'block'
461
+ : 'none';
462
+ this._buttons['collapse-expand'].enabled = enabled;
463
+ this._buttons['switch-view'].enabled = enabled;
464
+ this._buttons['shutdown-all'].enabled = enabled;
257
465
  }
258
466
  }
259
467
  /**
@@ -263,9 +471,10 @@ export class RunningSessions extends SidePanel {
263
471
  /**
264
472
  * Construct a new running widget.
265
473
  */
266
- constructor(managers, translator) {
474
+ constructor(managers, translator, stateDB) {
267
475
  super();
268
476
  this.managers = managers;
477
+ this._stateDB = stateDB !== null && stateDB !== void 0 ? stateDB : null;
269
478
  this.translator = translator !== null && translator !== void 0 ? translator : nullTranslator;
270
479
  const trans = this.translator.load('jupyterlab');
271
480
  this.addClass(RUNNING_CLASS);
@@ -287,6 +496,225 @@ export class RunningSessions extends SidePanel {
287
496
  this.managers.added.disconnect(this.addSection, this);
288
497
  super.dispose();
289
498
  }
499
+ /**
500
+ * Add a section for a new manager.
501
+ *
502
+ * @param managers Managers
503
+ * @param manager New manager
504
+ */
505
+ async addSection(_, manager) {
506
+ const section = new Section({ manager, translator: this.translator });
507
+ this.addWidget(section);
508
+ const state = await this._getState();
509
+ const sectionsInListView = state.listViewSections;
510
+ const sectionId = manager.name;
511
+ if (sectionsInListView && sectionsInListView.includes(sectionId)) {
512
+ section.toggleListView(true);
513
+ }
514
+ section.viewChanged.connect(async (_emitter, viewState) => {
515
+ await this._updateState(sectionId, viewState.mode);
516
+ });
517
+ }
518
+ /**
519
+ * Update state database with the new state of a given section.
520
+ */
521
+ async _updateState(sectionId, mode) {
522
+ var _a;
523
+ const state = await this._getState();
524
+ let listViewSections = (_a = state.listViewSections) !== null && _a !== void 0 ? _a : [];
525
+ if (mode === 'list' && !listViewSections.includes(sectionId)) {
526
+ listViewSections.push(sectionId);
527
+ }
528
+ else {
529
+ listViewSections = listViewSections.filter(e => e !== sectionId);
530
+ }
531
+ const newState = { listViewSections };
532
+ if (this._stateDB) {
533
+ await this._stateDB.save(STATE_DB_ID, newState);
534
+ }
535
+ }
536
+ /**
537
+ * Get current state from the state database.
538
+ */
539
+ async _getState() {
540
+ var _a;
541
+ if (!this._stateDB) {
542
+ return {};
543
+ }
544
+ return ((_a = (await this._stateDB.fetch(STATE_DB_ID))) !== null && _a !== void 0 ? _a : {});
545
+ }
546
+ }
547
+ /**
548
+ * Section but rendering its own title before the content
549
+ */
550
+ class TitledSection extends Section {
551
+ constructor(options) {
552
+ super(options);
553
+ const titleNode = document.createElement('h3');
554
+ titleNode.className = 'jp-SearchableSessions-title';
555
+ const label = titleNode.appendChild(document.createElement('span'));
556
+ label.className = 'jp-SearchableSessions-titleLabel';
557
+ label.textContent = this.title.label;
558
+ this.node.insertAdjacentElement('afterbegin', titleNode);
559
+ }
560
+ }
561
+ class EmptyIndicator extends Widget {
562
+ constructor(translator) {
563
+ super();
564
+ const trans = translator.load('jupyterlab');
565
+ this.addClass('jp-SearchableSessions-emptyIndicator');
566
+ this.node.textContent = trans.__('No matches');
567
+ }
568
+ }
569
+ /**
570
+ * A panel intended for use within `Dialog` to allow searching tabs and running sessions.
571
+ */
572
+ export class SearchableSessions extends Panel {
573
+ constructor(managers, translator) {
574
+ super();
575
+ this._activeIndex = 0;
576
+ this._translator = translator !== null && translator !== void 0 ? translator : nullTranslator;
577
+ this.addClass(RUNNING_CLASS);
578
+ this.addClass(SEARCHABLE_CLASS);
579
+ this._filterWidget = new FilterWidget(this._translator);
580
+ this.addWidget(this._filterWidget);
581
+ this._list = new SearchableSessionsList(managers, this._filterWidget, translator);
582
+ this.addWidget(this._list);
583
+ this._filterWidget.filterChanged.connect(() => {
584
+ this._activeIndex = 0;
585
+ this._updateActive(0);
586
+ }, this);
587
+ }
588
+ /**
589
+ * Dispose the resources held by the widget
590
+ */
591
+ dispose() {
592
+ if (this.isDisposed) {
593
+ return;
594
+ }
595
+ Signal.clearData(this);
596
+ super.dispose();
597
+ }
598
+ /**
599
+ * Click active element when the user confirmed the choice in the dialog.
600
+ */
601
+ getValue() {
602
+ const items = [
603
+ ...this.node.querySelectorAll('.' + ITEM_LABEL_CLASS)
604
+ ];
605
+ const pos = Math.min(Math.max(this._activeIndex, 0), items.length - 1);
606
+ items[pos].click();
607
+ }
608
+ /**
609
+ * Handle incoming events.
610
+ */
611
+ handleEvent(event) {
612
+ switch (event.type) {
613
+ case 'keydown':
614
+ this._evtKeydown(event);
615
+ break;
616
+ }
617
+ }
618
+ /**
619
+ * A message handler invoked on an `'after-attach'` message.
620
+ */
621
+ onAfterAttach(_) {
622
+ this._forceFocusInput();
623
+ this.node.addEventListener('keydown', this);
624
+ setTimeout(() => {
625
+ this._updateActive(0);
626
+ }, 0);
627
+ }
628
+ /**
629
+ * A message handler invoked on an `'after-detach'` message.
630
+ */
631
+ onAfterDetach(_) {
632
+ this.node.removeEventListener('keydown', this);
633
+ }
634
+ /**
635
+ * Force focus on the filter input.
636
+ *
637
+ * Note: forces focus because this widget is intended to be used in `Dialog`,
638
+ * which does not support focusing React widget nested within a non-React
639
+ * widget (a limitation of `focusNodeSelector` option implementation).
640
+ */
641
+ _forceFocusInput() {
642
+ var _a;
643
+ (_a = this._filterWidget.renderPromise) === null || _a === void 0 ? void 0 : _a.then(() => {
644
+ var _a;
645
+ (_a = this._filterWidget.node.querySelector('input')) === null || _a === void 0 ? void 0 : _a.focus();
646
+ }).catch(console.warn);
647
+ }
648
+ /**
649
+ * Navigate between items using up/down keys by shifting focus.
650
+ */
651
+ _evtKeydown(event) {
652
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
653
+ const direction = event.key === 'ArrowDown' ? +1 : -1;
654
+ const wasSet = this._updateActive(direction);
655
+ if (wasSet) {
656
+ event.preventDefault();
657
+ }
658
+ }
659
+ }
660
+ /**
661
+ * Set and mark active item relative to the current.
662
+ *
663
+ * Returns whether an active item was set.
664
+ */
665
+ _updateActive(direction) {
666
+ const items = [...this.node.querySelectorAll('.' + ITEM_CLASS)].filter(e => e.checkVisibility());
667
+ if (!items.length) {
668
+ return false;
669
+ }
670
+ for (const item of items) {
671
+ if (item.classList.contains('jp-mod-active')) {
672
+ item.classList.toggle('jp-mod-active', false);
673
+ }
674
+ }
675
+ const currentIndex = this._activeIndex;
676
+ let newIndex = null;
677
+ if (currentIndex === -1) {
678
+ // First or last
679
+ newIndex = direction === +1 ? 0 : items.length - 1;
680
+ }
681
+ else {
682
+ newIndex = Math.min(Math.max(currentIndex + direction, 0), items.length - 1);
683
+ }
684
+ if (newIndex !== null) {
685
+ items[newIndex].classList.add('jp-mod-active');
686
+ ElementExt.scrollIntoViewIfNeeded(this._list.node, items[newIndex]);
687
+ this._activeIndex = newIndex;
688
+ return true;
689
+ }
690
+ return false;
691
+ }
692
+ }
693
+ /**
694
+ * A panel list of searchable sessions.
695
+ */
696
+ export class SearchableSessionsList extends Panel {
697
+ constructor(managers, filterWidget, translator) {
698
+ super();
699
+ this._managers = managers;
700
+ this._translator = translator !== null && translator !== void 0 ? translator : nullTranslator;
701
+ this._filterWidget = filterWidget;
702
+ this.addClass('jp-SearchableSessions-list');
703
+ this._emptyIndicator = new EmptyIndicator(this._translator);
704
+ this.addWidget(this._emptyIndicator);
705
+ managers.items().forEach(manager => this.addSection(managers, manager));
706
+ managers.added.connect(this.addSection, this);
707
+ }
708
+ /**
709
+ * Dispose the resources held by the widget
710
+ */
711
+ dispose() {
712
+ if (this.isDisposed) {
713
+ return;
714
+ }
715
+ this._managers.added.disconnect(this.addSection, this);
716
+ super.dispose();
717
+ }
290
718
  /**
291
719
  * Add a section for a new manager.
292
720
  *
@@ -294,7 +722,17 @@ export class RunningSessions extends SidePanel {
294
722
  * @param manager New manager
295
723
  */
296
724
  addSection(_, manager) {
297
- this.addWidget(new Section({ manager, translator: this.translator }));
725
+ const section = new TitledSection({
726
+ manager,
727
+ translator: this._translator,
728
+ showToolbar: false,
729
+ filterProvider: this._filterWidget
730
+ });
731
+ // Do not use tree view in searchable list
732
+ section.toggleListView(true);
733
+ this.addWidget(section);
734
+ // Move empty indicator to the end
735
+ this.addWidget(this._emptyIndicator);
298
736
  }
299
737
  }
300
738
  //# sourceMappingURL=index.js.map