xtralab 0.14.0 → 0.15.0

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.
Files changed (52) hide show
  1. package/lib/customPanel/index.d.ts +7 -0
  2. package/lib/customPanel/index.js +74 -0
  3. package/lib/fileBrowser/commands.js +13 -1
  4. package/lib/fileBrowser/contents.d.ts +11 -0
  5. package/lib/fileBrowser/contents.js +28 -0
  6. package/lib/fileBrowser/dragAndDrop.d.ts +66 -0
  7. package/lib/fileBrowser/dragAndDrop.js +115 -0
  8. package/lib/fileBrowser/fileBrowser.d.ts +2 -0
  9. package/lib/fileBrowser/fileBrowser.js +151 -197
  10. package/lib/fileBrowser/index.js +39 -6
  11. package/lib/fileBrowser/widget.d.ts +44 -5
  12. package/lib/fileBrowser/widget.js +153 -14
  13. package/lib/index.js +4 -0
  14. package/lib/launcher/agents.d.ts +1 -1
  15. package/lib/launcher/agents.js +15 -2
  16. package/lib/launcher/icons.js +15 -4
  17. package/lib/launcher/schemaDefaults.d.ts +2 -9
  18. package/lib/launcher/schemaDefaults.js +37 -13
  19. package/lib/menuBar/icons.d.ts +6 -0
  20. package/lib/menuBar/icons.js +13 -0
  21. package/lib/menuBar/index.d.ts +15 -0
  22. package/lib/menuBar/index.js +164 -0
  23. package/lib/sidebar/index.d.ts +7 -13
  24. package/lib/sidebar/index.js +80 -46
  25. package/lib/terminals/index.d.ts +5 -3
  26. package/lib/terminals/index.js +17 -6
  27. package/lib/terminals/widget.d.ts +29 -25
  28. package/lib/terminals/widget.js +150 -65
  29. package/package.json +1 -1
  30. package/schema/launcher.json +2 -2
  31. package/schema/menu-bar.json +37 -0
  32. package/schema/sidebar.json +47 -7
  33. package/src/customPanel/index.ts +104 -0
  34. package/src/fileBrowser/commands.ts +14 -1
  35. package/src/fileBrowser/contents.ts +30 -0
  36. package/src/fileBrowser/dragAndDrop.ts +178 -0
  37. package/src/fileBrowser/fileBrowser.tsx +183 -225
  38. package/src/fileBrowser/index.ts +41 -6
  39. package/src/fileBrowser/widget.tsx +195 -14
  40. package/src/index.ts +4 -0
  41. package/src/launcher/agents.ts +16 -3
  42. package/src/launcher/icons.ts +16 -4
  43. package/src/launcher/schemaDefaults.ts +43 -13
  44. package/src/menuBar/icons.ts +14 -0
  45. package/src/menuBar/index.ts +195 -0
  46. package/src/sidebar/index.ts +95 -52
  47. package/src/terminals/index.ts +24 -6
  48. package/src/terminals/widget.tsx +186 -87
  49. package/style/base.css +22 -34
  50. package/style/customPanel.css +9 -0
  51. package/style/index.js +1 -0
  52. package/style/terminals.css +19 -77
@@ -1,3 +1,8 @@
1
+ import {
2
+ IMovableSectionDestination,
3
+ IMovableSectionSource,
4
+ ISectionEntry
5
+ } from '@jupyterlab/apputils';
1
6
  import {
2
7
  type TranslationBundle,
3
8
  nullTranslator
@@ -6,11 +11,16 @@ import {
6
11
  addIcon,
7
12
  closeIcon,
8
13
  LabIcon,
14
+ PanelWithToolbar,
9
15
  ReactWidget,
16
+ SidePanel,
10
17
  stopIcon,
11
18
  terminalIcon,
19
+ ToolbarButton,
12
20
  UseSignal
13
21
  } from '@jupyterlab/ui-components';
22
+ import { ISignal, Signal } from '@lumino/signaling';
23
+ import { AccordionPanel, Widget } from '@lumino/widgets';
14
24
  import * as React from 'react';
15
25
 
16
26
  import { SessionRegistry } from './model';
@@ -22,37 +32,134 @@ import { SessionRegistry } from './model';
22
32
  const RUNNING_TERMINALS_ID = 'xtralab-running-terminals';
23
33
 
24
34
  /**
25
- * Left-sidebar panel listing every running terminal session. It is the
26
- * sidebar counterpart of the launcher's terminal tiles: where the
27
- * launcher *starts* sessions, this panel surfaces the ones already
28
- * running so the user can jump back to a backgrounded agent — including
29
- * sessions whose tab has been closed but which are still alive on the
30
- * server. Each row is labelled with the session's title and, for a session
31
- * running a coding agent, a smaller line below it showing the agent's latest
32
- * line of output (from {@link SessionRegistry.activityFor}).
33
- *
34
- * Backed by {@link SessionRegistry}: a `UseSignal` re-renders the list
35
- * whenever the registry emits `stateChanged`, so it tracks
36
- * `runningChanged` and the agent-title cache without any local state.
37
- * Click behavior (activate vs. reopen, shutdown, new terminal) is
38
- * delegated to plugin-supplied callbacks so the widget never imports
39
- * `app`.
35
+ * Id of the accordion section holding the terminals list. Persisted by the
36
+ * movable-sections plugin, so it must stay stable across releases.
40
37
  */
41
- export class RunningTerminals extends ReactWidget {
38
+ const TERMINALS_SECTION_ID = 'xtralab-terminals-section';
39
+
40
+ /**
41
+ * Left-sidebar panel listing every running terminal session, including the
42
+ * sessions whose tab is closed but which are still alive on the server. The
43
+ * list is a single movable "Terminals" section.
44
+ */
45
+ export class RunningTerminals
46
+ extends SidePanel
47
+ implements IMovableSectionSource, IMovableSectionDestination
48
+ {
42
49
  constructor(options: RunningTerminals.IOptions) {
43
50
  super();
44
51
  this._registry = options.registry;
45
- this._trans = options.trans ?? nullTranslator.load('jupyterlab');
46
- this._iconForCommand = options.iconForCommand;
47
- this._onActivate = options.onActivate;
48
- this._onShutdown = options.onShutdown;
49
- this._onShutdownAll = options.onShutdownAll;
50
- this._onCreate = options.onCreate;
52
+ const trans = options.trans ?? nullTranslator.load('jupyterlab');
51
53
 
52
54
  this.id = RUNNING_TERMINALS_ID;
53
55
  this.title.icon = terminalIcon;
54
- this.title.caption = this._trans.__('Running Terminals');
56
+ this.title.caption = trans.__('Running Terminals');
55
57
  this.addClass('jp-xtralab-Terminals');
58
+
59
+ const section = (this._section = new PanelWithToolbar());
60
+ section.id = TERMINALS_SECTION_ID;
61
+ section.title.label = trans.__('Terminals');
62
+ section.addClass('jp-xtralab-Terminals-section');
63
+
64
+ const shutdownAll = new ToolbarButton({
65
+ icon: stopIcon,
66
+ onClick: options.onShutdownAll,
67
+ tooltip: trans.__('Shut Down All Terminals'),
68
+ enabled: this._registry.sessionNames().length > 0
69
+ });
70
+ this._registry.stateChanged.connect(() => {
71
+ shutdownAll.enabled = this._registry.sessionNames().length > 0;
72
+ });
73
+ section.toolbar.addItem('shutdown-all', shutdownAll);
74
+
75
+ const newTerminal = new ToolbarButton({
76
+ icon: addIcon,
77
+ onClick: () => {
78
+ // Anchor whatever the plugin shows (usually an agent menu) to the
79
+ // button's bottom-left, in the viewport coordinates `Menu.open` wants.
80
+ const rect = newTerminal.node.getBoundingClientRect();
81
+ options.onCreate({ x: rect.left, y: rect.bottom });
82
+ },
83
+ tooltip: trans.__('New Terminal')
84
+ });
85
+ section.toolbar.addItem('new-terminal', newTerminal);
86
+
87
+ section.addWidget(
88
+ new TerminalsListing({
89
+ registry: this._registry,
90
+ trans,
91
+ iconForCommand: options.iconForCommand,
92
+ onActivate: options.onActivate,
93
+ onShutdown: options.onShutdown
94
+ })
95
+ );
96
+
97
+ this.addWidget(section);
98
+ }
99
+
100
+ get accordionPanel(): AccordionPanel {
101
+ return this.content as AccordionPanel;
102
+ }
103
+
104
+ get sectionAdded(): ISignal<this, ISectionEntry> {
105
+ return this._sectionAdded;
106
+ }
107
+
108
+ get sections(): ReadonlyArray<Widget> {
109
+ return this.accordionPanel.widgets.filter(w => w !== this._section);
110
+ }
111
+
112
+ getSections(): ReadonlyArray<ISectionEntry> {
113
+ const entry = this._sectionEntry();
114
+ return entry ? [entry] : [];
115
+ }
116
+
117
+ removeSectionById(sectionId: string): Widget | null {
118
+ if (
119
+ sectionId !== TERMINALS_SECTION_ID ||
120
+ this._section.parent !== this.content
121
+ ) {
122
+ return null;
123
+ }
124
+ this._section.parent = null;
125
+ return this._section;
126
+ }
127
+
128
+ reinsertSection(widget: Widget): void {
129
+ this.addWidget(widget);
130
+ }
131
+
132
+ addSection(widget: Widget): void {
133
+ this.addWidget(widget);
134
+ }
135
+
136
+ removeSectionWidget(widget: Widget): void {
137
+ if (widget.parent === this.content) {
138
+ widget.parent = null;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * The layout restorer stores a section as collapsed whenever its panel is off
144
+ * screen at save time, so it can come back collapsed without the user asking.
145
+ */
146
+ expandOwnSection(): void {
147
+ const accordion = this.accordionPanel;
148
+ const index = Array.from(accordion.widgets).indexOf(this._section);
149
+ if (index >= 0) {
150
+ accordion.expand(index);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * The move plugin reconciles persisted moves only against sections announced
156
+ * after `registerSource`, and this panel builds its section in the constructor.
157
+ */
158
+ announceSections(): void {
159
+ const entry = this._sectionEntry();
160
+ if (entry) {
161
+ this._sectionAdded.emit(entry);
162
+ }
56
163
  }
57
164
 
58
165
  dispose(): void {
@@ -65,31 +172,23 @@ export class RunningTerminals extends ReactWidget {
65
172
  super.dispose();
66
173
  }
67
174
 
68
- protected render(): React.ReactElement {
69
- return (
70
- <UseSignal signal={this._registry.stateChanged}>
71
- {() => (
72
- <RunningTerminalsComponent
73
- registry={this._registry}
74
- trans={this._trans}
75
- iconForCommand={this._iconForCommand}
76
- onActivate={this._onActivate}
77
- onShutdown={this._onShutdown}
78
- onShutdownAll={this._onShutdownAll}
79
- onCreate={this._onCreate}
80
- />
81
- )}
82
- </UseSignal>
83
- );
175
+ private _sectionEntry(): ISectionEntry | null {
176
+ const accordion = this.accordionPanel;
177
+ const index = Array.from(accordion.widgets).indexOf(this._section);
178
+ const titleNode = accordion.titles[index] as HTMLElement | undefined;
179
+ if (index < 0 || !titleNode) {
180
+ return null;
181
+ }
182
+ return {
183
+ id: TERMINALS_SECTION_ID,
184
+ titleNode,
185
+ widget: this._section
186
+ };
84
187
  }
85
188
 
86
189
  private _registry: SessionRegistry;
87
- private _trans: TranslationBundle;
88
- private _iconForCommand: (command: string | null) => LabIcon;
89
- private _onActivate: (sessionName: string) => void;
90
- private _onShutdown: (sessionName: string) => void;
91
- private _onShutdownAll: () => void;
92
- private _onCreate: (anchor: { x: number; y: number }) => void;
190
+ private _section: PanelWithToolbar;
191
+ private _sectionAdded = new Signal<this, ISectionEntry>(this);
93
192
  }
94
193
 
95
194
  export namespace RunningTerminals {
@@ -128,24 +227,55 @@ export namespace RunningTerminals {
128
227
  }
129
228
  }
130
229
 
230
+ /**
231
+ * The body of the Terminals section.
232
+ */
233
+ class TerminalsListing extends ReactWidget {
234
+ constructor(options: {
235
+ registry: SessionRegistry;
236
+ trans: TranslationBundle;
237
+ iconForCommand: (command: string | null) => LabIcon;
238
+ onActivate: (sessionName: string) => void;
239
+ onShutdown: (sessionName: string) => void;
240
+ }) {
241
+ super();
242
+ this._options = options;
243
+ this.addClass('jp-xtralab-Terminals-listing');
244
+ }
245
+
246
+ protected render(): React.ReactElement {
247
+ return (
248
+ <UseSignal signal={this._options.registry.stateChanged}>
249
+ {() => (
250
+ <RunningTerminalsComponent
251
+ registry={this._options.registry}
252
+ trans={this._options.trans}
253
+ iconForCommand={this._options.iconForCommand}
254
+ onActivate={this._options.onActivate}
255
+ onShutdown={this._options.onShutdown}
256
+ />
257
+ )}
258
+ </UseSignal>
259
+ );
260
+ }
261
+
262
+ private _options: {
263
+ registry: SessionRegistry;
264
+ trans: TranslationBundle;
265
+ iconForCommand: (command: string | null) => LabIcon;
266
+ onActivate: (sessionName: string) => void;
267
+ onShutdown: (sessionName: string) => void;
268
+ };
269
+ }
270
+
131
271
  function RunningTerminalsComponent(props: {
132
272
  registry: SessionRegistry;
133
273
  trans: TranslationBundle;
134
274
  iconForCommand: (command: string | null) => LabIcon;
135
275
  onActivate: (sessionName: string) => void;
136
276
  onShutdown: (sessionName: string) => void;
137
- onShutdownAll: () => void;
138
- onCreate: (anchor: { x: number; y: number }) => void;
139
277
  }): React.ReactElement {
140
- const {
141
- registry,
142
- trans,
143
- iconForCommand,
144
- onActivate,
145
- onShutdown,
146
- onShutdownAll,
147
- onCreate
148
- } = props;
278
+ const { registry, trans, iconForCommand, onActivate, onShutdown } = props;
149
279
  const names = registry.sessionNames();
150
280
  // The session whose terminal is the current widget in the main area, so its
151
281
  // row can be highlighted. `null` when the current tab is a notebook or any
@@ -154,37 +284,6 @@ function RunningTerminalsComponent(props: {
154
284
 
155
285
  return (
156
286
  <div className="jp-xtralab-Terminals-body">
157
- <div className="jp-xtralab-Terminals-header">
158
- <h2 className="jp-xtralab-Terminals-title">{trans.__('Terminals')}</h2>
159
- <div className="jp-xtralab-Terminals-actions">
160
- <button
161
- type="button"
162
- className="jp-xtralab-Terminals-action jp-xtralab-Terminals-shutdown-all"
163
- onClick={onShutdownAll}
164
- disabled={names.length === 0}
165
- title={trans.__('Shut Down All Terminals')}
166
- aria-label={trans.__('Shut Down All Terminals')}
167
- >
168
- <stopIcon.react tag="span" verticalAlign="middle" />
169
- </button>
170
- <button
171
- type="button"
172
- className="jp-xtralab-Terminals-action jp-xtralab-Terminals-new"
173
- onClick={event => {
174
- // Anchor whatever the plugin shows (usually an agent menu) to
175
- // the button's bottom-left, in viewport coordinates — what
176
- // `Menu.open(x, y)` expects.
177
- const rect = event.currentTarget.getBoundingClientRect();
178
- onCreate({ x: rect.left, y: rect.bottom });
179
- }}
180
- title={trans.__('New Terminal')}
181
- aria-label={trans.__('New Terminal')}
182
- aria-haspopup="menu"
183
- >
184
- <addIcon.react tag="span" verticalAlign="middle" />
185
- </button>
186
- </div>
187
- </div>
188
287
  {names.length === 0 ? (
189
288
  <p className="jp-xtralab-Terminals-empty">
190
289
  {trans.__('No running terminals.')}
package/style/base.css CHANGED
@@ -4,10 +4,22 @@
4
4
  https://jupyterlab.readthedocs.io/en/stable/developer/css.html
5
5
  */
6
6
 
7
+ /*
8
+ * Rules live on the section class below, so they travel when the tree moves.
9
+ */
10
+ .jp-xtralab-FileBrowserPanel {
11
+ display: flex;
12
+ flex-direction: column;
13
+ min-width: 200px;
14
+ overflow: hidden;
15
+ }
16
+
7
17
  .jp-xtralab-FileBrowser {
8
18
  display: flex;
19
+ flex: 1 1 auto;
9
20
  flex-direction: column;
10
21
  min-width: 200px;
22
+ min-height: 0;
11
23
  overflow: hidden;
12
24
  background-color: var(--jp-layout-color1);
13
25
 
@@ -129,46 +141,22 @@
129
141
  min-height: 0;
130
142
  }
131
143
 
132
- .jp-xtralab-FileBrowser file-tree-container {
144
+ .jp-xtralab-FileBrowser-accordion {
133
145
  flex: 1 1 auto;
134
146
  min-height: 0;
135
147
  }
136
148
 
137
149
  /*
138
- * Drag image shown next to the cursor while dragging files out of the
139
- * tree. Mirrors the visual weight of the default file browser's drag
140
- * preview without depending on its DOM.
150
+ * In an accordion the toolbar shares the title row, so it must be able to
151
+ * shrink.
141
152
  */
142
- .jp-xtralab-DragImage {
143
- position: absolute;
144
- display: flex;
145
- align-items: center;
146
- gap: 4px;
147
- padding: 2px 6px;
148
- background-color: var(--jp-layout-color1);
149
- border: 1px solid var(--jp-border-color2);
150
- border-radius: 6px;
151
- color: var(--jp-ui-font-color1);
152
- font-family: var(--jp-ui-font-family);
153
- font-size: var(--jp-ui-font-size1);
154
- pointer-events: none;
155
- }
156
-
157
- .jp-xtralab-DragImage-icon {
158
- display: inline-flex;
159
- align-items: center;
153
+ .jp-AccordionPanel-title > .jp-xtralab-FileBrowser-toolbar {
154
+ flex: 0 1 auto;
155
+ min-width: 0;
156
+ overflow: hidden;
160
157
  }
161
158
 
162
- .jp-xtralab-DragImage-count {
163
- display: inline-flex;
164
- align-items: center;
165
- justify-content: center;
166
- min-width: 16px;
167
- height: 16px;
168
- padding: 0 4px;
169
- background-color: var(--jp-brand-color1);
170
- border-radius: 8px;
171
- color: var(--jp-ui-inverse-font-color1);
172
- font-size: var(--jp-ui-font-size0);
173
- line-height: 1;
159
+ .jp-xtralab-FileBrowser file-tree-container {
160
+ flex: 1 1 auto;
161
+ min-height: 0;
174
162
  }
@@ -0,0 +1,9 @@
1
+ /*
2
+ * All visible content comes from the hosted sections, which carry their own
3
+ * styles.
4
+ */
5
+
6
+ .jp-xtralab-CustomPanel {
7
+ min-width: 200px;
8
+ overflow: hidden;
9
+ }
package/style/index.js CHANGED
@@ -3,6 +3,7 @@ import './about.css';
3
3
  import './askAgent.css';
4
4
  import './chrome.css';
5
5
  import './commandBar.css';
6
+ import './customPanel.css';
6
7
  import './editorBreadcrumbs.css';
7
8
  import './git.css';
8
9
  import './highlight.css';
@@ -1,93 +1,36 @@
1
1
  /*
2
- * Left-sidebar panel listing running terminal sessions. Each row shows
3
- * the session's real (program-published) title, a smaller line below it
4
- * with the latest output from a running coding agent, and an inline
5
- * shutdown button that fades in on hover; the header carries the panel
6
- * title and a "new terminal" button. Colors and spacing pull from
7
- * `--jp-*` custom properties so the panel tracks the active JupyterLab
8
- * theme.
2
+ * Left-sidebar panel listing running terminal sessions, as a single movable
3
+ * "Terminals" section. Each row shows the session's real (program-published)
4
+ * title, a smaller line below it with the latest output from a running coding
5
+ * agent, and an inline shutdown button that fades in on hover. Colors and
6
+ * spacing pull from `--jp-*` custom properties so the panel tracks the active
7
+ * JupyterLab theme.
9
8
  */
10
9
 
11
10
  .jp-xtralab-Terminals {
12
- display: flex;
13
- flex-direction: column;
14
11
  min-width: 200px;
15
12
  overflow: hidden;
16
- background-color: var(--jp-layout-color1);
17
- color: var(--jp-ui-font-color1);
18
- font-size: var(--jp-ui-font-size1);
19
13
  }
20
14
 
21
- .jp-xtralab-Terminals-body {
15
+ /* On the section, not the panel, so the rules travel when the section moves. */
16
+ .jp-xtralab-Terminals-section {
22
17
  display: flex;
23
- flex: 1 1 auto;
24
18
  flex-direction: column;
25
- min-height: 0;
26
- }
27
-
28
- .jp-xtralab-Terminals-header {
29
- display: flex;
30
- flex: 0 0 auto;
31
- align-items: center;
32
- justify-content: space-between;
33
- padding: 8px 12px;
34
- border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
35
- }
36
-
37
- .jp-xtralab-Terminals-title {
38
- margin: 0;
39
- font-size: var(--jp-ui-font-size0);
40
- font-weight: 600;
41
- letter-spacing: 1px;
42
- text-transform: uppercase;
43
- color: var(--jp-ui-font-color1);
19
+ overflow: hidden;
44
20
  }
45
21
 
46
- .jp-xtralab-Terminals-actions {
22
+ .jp-xtralab-Terminals-listing {
47
23
  display: flex;
48
- flex: 0 0 auto;
49
- align-items: center;
50
- gap: 2px;
24
+ flex: 1 1 auto;
25
+ flex-direction: column;
26
+ min-height: 0;
51
27
  }
52
28
 
53
- .jp-xtralab-Terminals-action {
29
+ .jp-xtralab-Terminals-body {
54
30
  display: flex;
55
- align-items: center;
56
- justify-content: center;
57
- width: 20px;
58
- height: 20px;
59
- padding: 0;
60
- background: transparent;
61
- border: none;
62
- border-radius: 2px;
63
- color: var(--jp-ui-font-color2);
64
- cursor: pointer;
65
- }
66
-
67
- .jp-xtralab-Terminals-action:hover {
68
- background-color: var(--jp-layout-color2);
69
- color: var(--jp-ui-font-color1);
70
- }
71
-
72
- .jp-xtralab-Terminals-action:focus-visible {
73
- outline: var(--jp-border-width) solid var(--jp-brand-color1);
74
- outline-offset: -1px;
75
- }
76
-
77
- /*
78
- * "Shut down all" is disabled when there are no running terminals. Dim the
79
- * whole control with opacity rather than `color`: the stop glyph is an SVG
80
- * whose fill comes from its own `jp-icon3` class (not `currentColor`), so a
81
- * `color` change alone leaves the icon looking fully active — which is why the
82
- * button did not read as disabled.
83
- */
84
- .jp-xtralab-Terminals-action:disabled {
85
- cursor: default;
86
- opacity: 0.4;
87
- }
88
-
89
- .jp-xtralab-Terminals-action:disabled:hover {
90
- background: transparent;
31
+ flex: 1 1 auto;
32
+ flex-direction: column;
33
+ min-height: 0;
91
34
  }
92
35
 
93
36
  .jp-xtralab-Terminals-empty {
@@ -214,9 +157,8 @@
214
157
  opacity: 1;
215
158
  }
216
159
 
217
- /* Keep the row/header icons at the standard 16px sidebar size. */
218
- .jp-xtralab-Terminals-item svg,
219
- .jp-xtralab-Terminals-action svg {
160
+ /* Keep the row icons at the standard 16px sidebar size. */
161
+ .jp-xtralab-Terminals-item svg {
220
162
  width: 16px;
221
163
  height: 16px;
222
164
  }