xtralab 0.4.0 → 0.6.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.
- package/README.md +110 -73
- package/lib/agentSessions.d.ts +50 -0
- package/lib/agentSessions.js +45 -0
- package/lib/fileBrowser/index.js +5 -1
- package/lib/git/commands.d.ts +12 -13
- package/lib/git/commands.js +7 -28
- package/lib/git/diffProvider.d.ts +8 -0
- package/lib/git/diffProvider.js +314 -0
- package/lib/git/diffSurface.d.ts +115 -0
- package/lib/git/diffSurface.js +342 -0
- package/lib/git/diffWidget.d.ts +8 -13
- package/lib/git/diffWidget.js +65 -311
- package/lib/git/imageDiff.d.ts +23 -0
- package/lib/git/imageDiff.js +256 -0
- package/lib/git/index.d.ts +10 -8
- package/lib/git/index.js +37 -52
- package/lib/index.d.ts +4 -2
- package/lib/index.js +14 -8
- package/lib/launcher/agents.d.ts +1 -1
- package/lib/launcher/agents.js +22 -13
- package/lib/launcher/commands.d.ts +32 -6
- package/lib/launcher/commands.js +81 -57
- package/lib/launcher/dashboard.d.ts +15 -0
- package/lib/launcher/dashboard.js +119 -5
- package/lib/launcher/editorRegistry.d.ts +43 -0
- package/lib/launcher/editorRegistry.js +96 -0
- package/lib/launcher/editors.d.ts +78 -0
- package/lib/launcher/editors.js +110 -0
- package/lib/launcher/icons.d.ts +25 -6
- package/lib/launcher/icons.js +116 -19
- package/lib/launcher/index.d.ts +2 -22
- package/lib/launcher/index.js +48 -25
- package/lib/launcher/registry.d.ts +21 -0
- package/lib/launcher/registry.js +28 -0
- package/lib/launcher/tokens.d.ts +41 -0
- package/lib/launcher/tokens.js +17 -0
- package/lib/terminals/detection.d.ts +11 -0
- package/lib/terminals/detection.js +50 -0
- package/lib/terminals/index.d.ts +27 -0
- package/lib/terminals/index.js +194 -0
- package/lib/terminals/model.d.ts +191 -0
- package/lib/terminals/model.js +418 -0
- package/lib/terminals/widget.d.ts +72 -0
- package/lib/terminals/widget.js +93 -0
- package/lib/topBar/icons.d.ts +13 -0
- package/lib/topBar/icons.js +27 -0
- package/lib/topBar/index.d.ts +16 -0
- package/lib/topBar/index.js +86 -0
- package/package.json +52 -35
- package/schema/launcher.json +56 -1
- package/src/agentSessions.ts +94 -0
- package/src/fileBrowser/index.ts +5 -1
- package/src/git/commands.ts +12 -38
- package/src/git/diffProvider.tsx +462 -0
- package/src/git/diffSurface.tsx +588 -0
- package/src/git/diffWidget.tsx +105 -465
- package/src/git/imageDiff.tsx +469 -0
- package/src/git/index.ts +38 -68
- package/src/index.ts +14 -8
- package/src/launcher/agents.ts +23 -14
- package/src/launcher/commands.ts +98 -60
- package/src/launcher/dashboard.tsx +205 -3
- package/src/launcher/editorRegistry.ts +149 -0
- package/src/launcher/editors.ts +183 -0
- package/src/launcher/icons.ts +120 -20
- package/src/launcher/index.ts +62 -27
- package/src/launcher/registry.ts +33 -0
- package/src/launcher/tokens.ts +51 -0
- package/src/terminals/detection.ts +65 -0
- package/src/terminals/index.ts +222 -0
- package/src/terminals/model.ts +497 -0
- package/src/terminals/widget.tsx +240 -0
- package/src/topBar/icons.ts +30 -0
- package/src/topBar/index.ts +121 -0
- package/style/git.css +267 -266
- package/style/index.css +2 -1
- package/style/index.js +2 -1
- package/style/launcher.css +124 -2
- package/style/terminals.css +235 -0
- package/style/topBar.css +38 -0
- package/lib/git/icons.d.ts +0 -14
- package/lib/git/icons.js +0 -19
- package/lib/git/panel.d.ts +0 -29
- package/lib/git/panel.js +0 -42
- package/lib/git/panelComponent.d.ts +0 -28
- package/lib/git/panelComponent.js +0 -320
- package/lib/statusBar/index.d.ts +0 -14
- package/lib/statusBar/index.js +0 -89
- package/lib/statusBar/widget.d.ts +0 -121
- package/lib/statusBar/widget.js +0 -237
- package/src/git/icons.ts +0 -20
- package/src/git/panel.tsx +0 -56
- package/src/git/panelComponent.tsx +0 -575
- package/src/statusBar/index.ts +0 -115
- package/src/statusBar/widget.tsx +0 -318
- package/style/statusBar.css +0 -95
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type TranslationBundle,
|
|
3
|
+
nullTranslator
|
|
4
|
+
} from '@jupyterlab/translation';
|
|
5
|
+
import {
|
|
6
|
+
addIcon,
|
|
7
|
+
closeIcon,
|
|
8
|
+
LabIcon,
|
|
9
|
+
ReactWidget,
|
|
10
|
+
stopIcon,
|
|
11
|
+
terminalIcon,
|
|
12
|
+
UseSignal
|
|
13
|
+
} from '@jupyterlab/ui-components';
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
|
|
16
|
+
import { SessionRegistry } from './model';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Id of the panel widget. Used for layout restoration and as the handle
|
|
20
|
+
* the sidebar visibility toggle would target.
|
|
21
|
+
*/
|
|
22
|
+
export const RUNNING_TERMINALS_ID = 'xtralab-running-terminals';
|
|
23
|
+
|
|
24
|
+
/**
|
|
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.
|
|
31
|
+
*
|
|
32
|
+
* Backed by {@link SessionRegistry}: a `UseSignal` re-renders the list
|
|
33
|
+
* whenever the registry emits `stateChanged`, so it tracks
|
|
34
|
+
* `runningChanged` and the agent-title cache without any local state.
|
|
35
|
+
* Click behaviour (activate vs. reopen, shutdown, new terminal) is
|
|
36
|
+
* delegated to plugin-supplied callbacks so the widget never imports
|
|
37
|
+
* `app`.
|
|
38
|
+
*/
|
|
39
|
+
export class RunningTerminals extends ReactWidget {
|
|
40
|
+
constructor(options: RunningTerminals.IOptions) {
|
|
41
|
+
super();
|
|
42
|
+
this._registry = options.registry;
|
|
43
|
+
this._trans = options.trans ?? nullTranslator.load('jupyterlab');
|
|
44
|
+
this._iconForCommand = options.iconForCommand;
|
|
45
|
+
this._onActivate = options.onActivate;
|
|
46
|
+
this._onShutdown = options.onShutdown;
|
|
47
|
+
this._onShutdownAll = options.onShutdownAll;
|
|
48
|
+
this._onCreate = options.onCreate;
|
|
49
|
+
|
|
50
|
+
this.id = RUNNING_TERMINALS_ID;
|
|
51
|
+
this.title.icon = terminalIcon;
|
|
52
|
+
this.title.caption = this._trans.__('Running Terminals');
|
|
53
|
+
this.addClass('jp-xtralab-Terminals');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
dispose(): void {
|
|
57
|
+
if (this.isDisposed) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// The panel owns the registry, so tear down its upstream
|
|
61
|
+
// subscriptions before the React tree goes away.
|
|
62
|
+
this._registry.dispose();
|
|
63
|
+
super.dispose();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected render(): React.ReactElement {
|
|
67
|
+
return (
|
|
68
|
+
<UseSignal signal={this._registry.stateChanged}>
|
|
69
|
+
{() => (
|
|
70
|
+
<RunningTerminalsComponent
|
|
71
|
+
registry={this._registry}
|
|
72
|
+
trans={this._trans}
|
|
73
|
+
iconForCommand={this._iconForCommand}
|
|
74
|
+
onActivate={this._onActivate}
|
|
75
|
+
onShutdown={this._onShutdown}
|
|
76
|
+
onShutdownAll={this._onShutdownAll}
|
|
77
|
+
onCreate={this._onCreate}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
</UseSignal>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private _registry: SessionRegistry;
|
|
85
|
+
private _trans: TranslationBundle;
|
|
86
|
+
private _iconForCommand: (command: string | null) => LabIcon;
|
|
87
|
+
private _onActivate: (sessionName: string) => void;
|
|
88
|
+
private _onShutdown: (sessionName: string) => void;
|
|
89
|
+
private _onShutdownAll: () => void;
|
|
90
|
+
private _onCreate: (anchor: { x: number; y: number }) => void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export namespace RunningTerminals {
|
|
94
|
+
export interface IOptions {
|
|
95
|
+
registry: SessionRegistry;
|
|
96
|
+
trans?: TranslationBundle;
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the running-agent command for a row (from
|
|
99
|
+
* `registry.agentCommandFor`) to the icon to show before its label —
|
|
100
|
+
* the agent's logo, or the plain terminal icon. Supplied by the plugin
|
|
101
|
+
* so the widget never imports the agent list.
|
|
102
|
+
*/
|
|
103
|
+
iconForCommand: (command: string | null) => LabIcon;
|
|
104
|
+
/**
|
|
105
|
+
* Activate the named session's open tab, or reopen it in a fresh
|
|
106
|
+
* terminal widget if no tab is currently attached.
|
|
107
|
+
*/
|
|
108
|
+
onActivate: (sessionName: string) => void;
|
|
109
|
+
/** Shut the named session down on the server. */
|
|
110
|
+
onShutdown: (sessionName: string) => void;
|
|
111
|
+
/**
|
|
112
|
+
* Shut down every running terminal at once. The plugin is expected to
|
|
113
|
+
* confirm with the user first, since it tears down all live sessions.
|
|
114
|
+
*/
|
|
115
|
+
onShutdownAll: () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Activate the "+" button, anchored at the given viewport coordinates
|
|
118
|
+
* (the bottom-left of the button). The plugin decides what to show
|
|
119
|
+
* there — a menu of agents plus a plain terminal, or just a new
|
|
120
|
+
* terminal when no agents are available — so the panel only reports
|
|
121
|
+
* where the button is and never imports the command/menu machinery.
|
|
122
|
+
*/
|
|
123
|
+
onCreate: (anchor: { x: number; y: number }) => void;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function RunningTerminalsComponent(props: {
|
|
128
|
+
registry: SessionRegistry;
|
|
129
|
+
trans: TranslationBundle;
|
|
130
|
+
iconForCommand: (command: string | null) => LabIcon;
|
|
131
|
+
onActivate: (sessionName: string) => void;
|
|
132
|
+
onShutdown: (sessionName: string) => void;
|
|
133
|
+
onShutdownAll: () => void;
|
|
134
|
+
onCreate: (anchor: { x: number; y: number }) => void;
|
|
135
|
+
}): React.ReactElement {
|
|
136
|
+
const {
|
|
137
|
+
registry,
|
|
138
|
+
trans,
|
|
139
|
+
iconForCommand,
|
|
140
|
+
onActivate,
|
|
141
|
+
onShutdown,
|
|
142
|
+
onShutdownAll,
|
|
143
|
+
onCreate
|
|
144
|
+
} = props;
|
|
145
|
+
const names = registry.sessionNames();
|
|
146
|
+
// The session whose terminal is the current widget in the main area, so its
|
|
147
|
+
// row can be highlighted. `null` when the current tab is a notebook or any
|
|
148
|
+
// other non-terminal widget.
|
|
149
|
+
const currentName = registry.currentSessionName();
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="jp-xtralab-Terminals-body">
|
|
153
|
+
<div className="jp-xtralab-Terminals-header">
|
|
154
|
+
<h2 className="jp-xtralab-Terminals-title">{trans.__('Terminals')}</h2>
|
|
155
|
+
<div className="jp-xtralab-Terminals-actions">
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
className="jp-xtralab-Terminals-action jp-xtralab-Terminals-shutdown-all"
|
|
159
|
+
onClick={onShutdownAll}
|
|
160
|
+
disabled={names.length === 0}
|
|
161
|
+
title={trans.__('Shut Down All Terminals')}
|
|
162
|
+
aria-label={trans.__('Shut Down All Terminals')}
|
|
163
|
+
>
|
|
164
|
+
<stopIcon.react tag="span" verticalAlign="middle" />
|
|
165
|
+
</button>
|
|
166
|
+
<button
|
|
167
|
+
type="button"
|
|
168
|
+
className="jp-xtralab-Terminals-action jp-xtralab-Terminals-new"
|
|
169
|
+
onClick={event => {
|
|
170
|
+
// Anchor whatever the plugin shows (usually an agent menu) to
|
|
171
|
+
// the button's bottom-left, in viewport coordinates — what
|
|
172
|
+
// `Menu.open(x, y)` expects.
|
|
173
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
174
|
+
onCreate({ x: rect.left, y: rect.bottom });
|
|
175
|
+
}}
|
|
176
|
+
title={trans.__('New Terminal')}
|
|
177
|
+
aria-label={trans.__('New Terminal')}
|
|
178
|
+
aria-haspopup="menu"
|
|
179
|
+
>
|
|
180
|
+
<addIcon.react tag="span" verticalAlign="middle" />
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
{names.length === 0 ? (
|
|
185
|
+
<p className="jp-xtralab-Terminals-empty">
|
|
186
|
+
{trans.__('No running terminals.')}
|
|
187
|
+
</p>
|
|
188
|
+
) : (
|
|
189
|
+
<ul className="jp-xtralab-Terminals-list">
|
|
190
|
+
{names.map(name => {
|
|
191
|
+
const label = registry.labelFor(name);
|
|
192
|
+
const hasWidget = registry.widgetFor(name) !== null;
|
|
193
|
+
const tooltip = hasWidget
|
|
194
|
+
? trans.__('Activate %1', label)
|
|
195
|
+
: trans.__('Reopen %1', label);
|
|
196
|
+
// The running agent's logo (e.g. Claude), or the plain terminal
|
|
197
|
+
// icon when nothing recognised is running in the session.
|
|
198
|
+
const RowIcon = iconForCommand(
|
|
199
|
+
registry.agentCommandFor(name)
|
|
200
|
+
).react;
|
|
201
|
+
const isCurrent = name === currentName;
|
|
202
|
+
return (
|
|
203
|
+
<li
|
|
204
|
+
key={name}
|
|
205
|
+
className={
|
|
206
|
+
isCurrent
|
|
207
|
+
? 'jp-xtralab-Terminals-item jp-mod-current'
|
|
208
|
+
: 'jp-xtralab-Terminals-item'
|
|
209
|
+
}
|
|
210
|
+
>
|
|
211
|
+
<button
|
|
212
|
+
type="button"
|
|
213
|
+
className="jp-xtralab-Terminals-item-activate"
|
|
214
|
+
onClick={() => onActivate(name)}
|
|
215
|
+
title={tooltip}
|
|
216
|
+
aria-label={tooltip}
|
|
217
|
+
aria-current={isCurrent || undefined}
|
|
218
|
+
>
|
|
219
|
+
<RowIcon tag="span" verticalAlign="middle" />
|
|
220
|
+
<span className="jp-xtralab-Terminals-item-label">
|
|
221
|
+
{label}
|
|
222
|
+
</span>
|
|
223
|
+
</button>
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
className="jp-xtralab-Terminals-item-close"
|
|
227
|
+
onClick={() => onShutdown(name)}
|
|
228
|
+
title={trans.__('Shut down %1', label)}
|
|
229
|
+
aria-label={trans.__('Shut down %1', label)}
|
|
230
|
+
>
|
|
231
|
+
<closeIcon.react tag="span" verticalAlign="middle" />
|
|
232
|
+
</button>
|
|
233
|
+
</li>
|
|
234
|
+
);
|
|
235
|
+
})}
|
|
236
|
+
</ul>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Mirror-symmetric "toggle sidebar" marks for the top-bar buttons, in the
|
|
5
|
+
* same visual language as the macOS / VS Code / svgrepo sidebar icons
|
|
6
|
+
* (https://www.svgrepo.com/vectors/sidebar/): a rounded window outline split
|
|
7
|
+
* by a divider, with the controlled side shaded so left vs. right reads at a
|
|
8
|
+
* glance. The artwork is hand-authored from basic geometry (rounded rect +
|
|
9
|
+
* divider + a shaded panel) so there is no third-party licensing to track,
|
|
10
|
+
* and every stroke/fill uses `currentColor` so the icons inherit the top
|
|
11
|
+
* bar's text color across the light and dark themes.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const leftSidebarIcon = new LabIcon({
|
|
15
|
+
name: 'xtralab:sidebar-left',
|
|
16
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
17
|
+
<path d="M9 3H5.5A2.5 2.5 0 0 0 3 5.5V18.5A2.5 2.5 0 0 0 5.5 21H9Z" fill="currentColor" fill-opacity="0.3"/>
|
|
18
|
+
<rect x="3" y="3" width="18" height="18" rx="2.5" stroke="currentColor" stroke-width="1.7"/>
|
|
19
|
+
<line x1="9" y1="3" x2="9" y2="21" stroke="currentColor" stroke-width="1.7"/>
|
|
20
|
+
</svg>`
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const rightSidebarIcon = new LabIcon({
|
|
24
|
+
name: 'xtralab:sidebar-right',
|
|
25
|
+
svgstr: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
|
26
|
+
<path d="M15 3H18.5A2.5 2.5 0 0 1 21 5.5V18.5A2.5 2.5 0 0 1 18.5 21H15Z" fill="currentColor" fill-opacity="0.3"/>
|
|
27
|
+
<rect x="3" y="3" width="18" height="18" rx="2.5" stroke="currentColor" stroke-width="1.7"/>
|
|
28
|
+
<line x1="15" y1="3" x2="15" y2="21" stroke="currentColor" stroke-width="1.7"/>
|
|
29
|
+
</svg>`
|
|
30
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ILabShell,
|
|
3
|
+
JupyterFrontEnd,
|
|
4
|
+
JupyterFrontEndPlugin
|
|
5
|
+
} from '@jupyterlab/application';
|
|
6
|
+
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
|
|
7
|
+
import { CommandToolbarButton, LabIcon } from '@jupyterlab/ui-components';
|
|
8
|
+
|
|
9
|
+
import { leftSidebarIcon, rightSidebarIcon } from './icons';
|
|
10
|
+
|
|
11
|
+
const PLUGIN_ID = 'xtralab:top-bar';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Upstream commands from `@jupyterlab/application-extension` that collapse or
|
|
15
|
+
* expand the side areas. We bind the buttons to these rather than
|
|
16
|
+
* reimplementing the toggle, so behavior (and the `isToggled`/`isEnabled`
|
|
17
|
+
* state the buttons reflect) stays in lockstep with the View menu entries
|
|
18
|
+
* and their keyboard shortcuts.
|
|
19
|
+
*/
|
|
20
|
+
const TOGGLE_LEFT_AREA = 'application:toggle-left-area';
|
|
21
|
+
const TOGGLE_RIGHT_AREA = 'application:toggle-right-area';
|
|
22
|
+
|
|
23
|
+
interface IButtonSpec {
|
|
24
|
+
/** Stable widget id (required by `LabShell.add`). */
|
|
25
|
+
id: string;
|
|
26
|
+
/** Command the button triggers and mirrors the state of. */
|
|
27
|
+
command: string;
|
|
28
|
+
icon: LabIcon;
|
|
29
|
+
caption: (trans: ReturnType<ITranslator['load']>) => string;
|
|
30
|
+
/**
|
|
31
|
+
* Rank within the `top` area. The main menu bar is added at rank 100, so a
|
|
32
|
+
* negative rank lands the left button at the leading edge before it; a large
|
|
33
|
+
* rank puts the right button after the menu, where `margin-left: auto`
|
|
34
|
+
* (style/topBar.css) floats it to the far edge. xtralab's shipped config
|
|
35
|
+
* disables the upstream Jupyter logo that otherwise occupies rank 0, so the
|
|
36
|
+
* leading edge is free.
|
|
37
|
+
*/
|
|
38
|
+
rank: number;
|
|
39
|
+
/** Side-specific class, used by the stylesheet for placement. */
|
|
40
|
+
sideClass: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const BUTTONS: IButtonSpec[] = [
|
|
44
|
+
{
|
|
45
|
+
id: 'xtralab-toggle-left-sidebar',
|
|
46
|
+
command: TOGGLE_LEFT_AREA,
|
|
47
|
+
icon: leftSidebarIcon,
|
|
48
|
+
caption: trans => trans.__('Toggle left sidebar'),
|
|
49
|
+
rank: -1,
|
|
50
|
+
sideClass: 'xtralab-TopBarButton-left'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'xtralab-toggle-right-sidebar',
|
|
54
|
+
command: TOGGLE_RIGHT_AREA,
|
|
55
|
+
icon: rightSidebarIcon,
|
|
56
|
+
caption: trans => trans.__('Toggle right sidebar'),
|
|
57
|
+
rank: 1000,
|
|
58
|
+
sideClass: 'xtralab-TopBarButton-right'
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Add two icon buttons to the top bar that toggle the left and right side
|
|
64
|
+
* areas, mirroring the macOS-style sidebar buttons: a left-sidebar button at
|
|
65
|
+
* the leading edge and a right-sidebar button at the far edge.
|
|
66
|
+
*
|
|
67
|
+
* Each is a `CommandToolbarButton` wrapping an existing
|
|
68
|
+
* `application:toggle-{left,right}-area` command, so clicking it runs the
|
|
69
|
+
* same toggle as the View menu and the button reflects the command's state —
|
|
70
|
+
* pressed while the matching sidebar is open, and disabled when the area is
|
|
71
|
+
* empty. Note xtralab ships nothing in the right area by default (the
|
|
72
|
+
* property inspector, table of contents and debugger are all disabled), so
|
|
73
|
+
* the right button stays disabled until a widget is moved or added there.
|
|
74
|
+
*/
|
|
75
|
+
const plugin: JupyterFrontEndPlugin<void> = {
|
|
76
|
+
id: PLUGIN_ID,
|
|
77
|
+
description: 'Add left and right sidebar toggle buttons to the top bar.',
|
|
78
|
+
autoStart: true,
|
|
79
|
+
requires: [ILabShell],
|
|
80
|
+
optional: [ITranslator],
|
|
81
|
+
activate: (
|
|
82
|
+
app: JupyterFrontEnd,
|
|
83
|
+
labShell: ILabShell,
|
|
84
|
+
translator: ITranslator | null
|
|
85
|
+
): void => {
|
|
86
|
+
const { commands } = app;
|
|
87
|
+
const trans = (translator ?? nullTranslator).load('jupyterlab');
|
|
88
|
+
|
|
89
|
+
// Defer to `app.restored` so the upstream toggle commands are already in
|
|
90
|
+
// the registry: `CommandToolbarButton` renders nothing until its command
|
|
91
|
+
// exists and only re-renders on `commandChanged` of type `changed` /
|
|
92
|
+
// `many-changed` (not `added`), so mounting earlier could leave a button
|
|
93
|
+
// blank until the next unrelated command change.
|
|
94
|
+
void app.restored.then(() => {
|
|
95
|
+
for (const spec of BUTTONS) {
|
|
96
|
+
const button = new CommandToolbarButton({
|
|
97
|
+
commands,
|
|
98
|
+
id: spec.command,
|
|
99
|
+
icon: spec.icon,
|
|
100
|
+
// Empty label keeps the button icon-only: the override is forwarded
|
|
101
|
+
// verbatim and the component skips the label span when it is falsy.
|
|
102
|
+
label: '',
|
|
103
|
+
caption: spec.caption(trans),
|
|
104
|
+
// Toggle on mousedown without focusing the button. Otherwise
|
|
105
|
+
// ToolbarButtonComponent calls `event.target.focus()` on click,
|
|
106
|
+
// leaving the stealth button showing a focus fill/outline (the
|
|
107
|
+
// stray "border") until you click elsewhere — unwanted chrome for a
|
|
108
|
+
// title-bar control. Keyboard Tab focus still works, so this stays
|
|
109
|
+
// accessible.
|
|
110
|
+
noFocusOnClick: true
|
|
111
|
+
});
|
|
112
|
+
button.id = spec.id;
|
|
113
|
+
button.addClass('xtralab-TopBarButton');
|
|
114
|
+
button.addClass(spec.sideClass);
|
|
115
|
+
labShell.add(button, 'top', { rank: spec.rank });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export default plugin;
|