xtralab 0.2.0 → 0.4.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 +126 -125
- package/lib/editorBreadcrumbs/index.d.ts +11 -0
- package/lib/editorBreadcrumbs/index.js +41 -0
- package/lib/editorBreadcrumbs/widget.d.ts +34 -0
- package/lib/editorBreadcrumbs/widget.js +131 -0
- package/lib/editorIndent/index.d.ts +17 -0
- package/lib/editorIndent/index.js +142 -0
- package/lib/fileBrowser/commands.d.ts +5 -2
- package/lib/fileBrowser/commands.js +42 -3
- package/lib/fileBrowser/fileBrowser.js +394 -11
- package/lib/fileBrowser/gitStatus.d.ts +13 -0
- package/lib/fileBrowser/gitStatus.js +60 -0
- package/lib/fileBrowser/icons.d.ts +4 -4
- package/lib/fileBrowser/icons.js +4 -4
- package/lib/fileBrowser/widget.d.ts +50 -2
- package/lib/fileBrowser/widget.js +26 -2
- package/lib/git/icons.d.ts +1 -1
- package/lib/git/icons.js +1 -1
- package/lib/git/panelComponent.js +28 -11
- package/lib/index.js +4 -0
- package/lib/launcher/dashboard.js +78 -16
- package/package.json +22 -2
- package/src/editorBreadcrumbs/index.ts +70 -0
- package/src/editorBreadcrumbs/widget.ts +158 -0
- package/src/editorIndent/index.ts +177 -0
- package/src/fileBrowser/commands.ts +45 -3
- package/src/fileBrowser/fileBrowser.tsx +428 -12
- package/src/fileBrowser/gitStatus.ts +66 -0
- package/src/fileBrowser/icons.ts +4 -4
- package/src/fileBrowser/widget.tsx +80 -3
- package/src/git/icons.ts +1 -1
- package/src/git/panelComponent.tsx +29 -15
- package/src/index.ts +4 -0
- package/src/launcher/dashboard.tsx +135 -16
- package/style/editorBreadcrumbs.css +86 -0
- package/style/index.css +2 -0
- package/style/index.js +1 -0
- package/style/launcher.css +0 -5
- package/style/tabs.css +66 -0
|
@@ -12,7 +12,7 @@ import { xtralabFileBrowserIcon } from './icons';
|
|
|
12
12
|
export const FILE_BROWSER_ID = 'xtralab:file-browser';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The CSS class added to the
|
|
15
|
+
* The CSS class added to the xtralab file browser widget. The selectors that
|
|
16
16
|
* bind application context menu items hang off this class, so it must remain
|
|
17
17
|
* specific enough not to clash with other browsers (such as the default
|
|
18
18
|
* `jp-FileBrowser`).
|
|
@@ -29,7 +29,7 @@ export interface IXtralabFileBrowserOptions {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Public API of the
|
|
32
|
+
* Public API of the xtralab file browser used by command handlers and other
|
|
33
33
|
* collaborators. Kept narrow on purpose so the React component stays free to
|
|
34
34
|
* evolve the underlying tree integration.
|
|
35
35
|
*/
|
|
@@ -74,6 +74,31 @@ export interface IXtralabFileBrowser {
|
|
|
74
74
|
*/
|
|
75
75
|
readonly pathAdded: ISignal<IXtralabFileBrowser, string>;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Emits when an external caller asks the tree to scroll to and select
|
|
79
|
+
* the given canonical path. The React component listens, lazily loads
|
|
80
|
+
* any unloaded ancestor directories, expands them, and selects the
|
|
81
|
+
* target. Used by the editor breadcrumbs to jump back to a file or
|
|
82
|
+
* folder shown in the breadcrumb trail.
|
|
83
|
+
*/
|
|
84
|
+
readonly revealRequested: ISignal<IXtralabFileBrowser, string>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Emits when an external caller asks the tree to return to the
|
|
88
|
+
* workspace root: clear any current selection and scroll back to the
|
|
89
|
+
* first row. Distinct from {@link revealRequested} because the
|
|
90
|
+
* workspace root has no tree row of its own — it cannot be reached
|
|
91
|
+
* by passing a path.
|
|
92
|
+
*/
|
|
93
|
+
readonly rootRequested: ISignal<IXtralabFileBrowser, void>;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Emits when an external caller asks the tree to collapse every
|
|
97
|
+
* expanded folder. The React component listens and walks the loaded
|
|
98
|
+
* directories, calling `.collapse()` on each expanded one.
|
|
99
|
+
*/
|
|
100
|
+
readonly collapseAllRequested: ISignal<IXtralabFileBrowser, void>;
|
|
101
|
+
|
|
77
102
|
/** Trigger a refresh of every loaded directory in the tree. */
|
|
78
103
|
refresh(): void;
|
|
79
104
|
|
|
@@ -83,6 +108,26 @@ export interface IXtralabFileBrowser {
|
|
|
83
108
|
* convention: directories carry a trailing slash, files do not.
|
|
84
109
|
*/
|
|
85
110
|
notifyPathAdded(canonicalPath: string): void;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ask the tree to reveal {@link canonicalPath}: load and expand any
|
|
114
|
+
* unloaded ancestor directories, then select and scroll the target
|
|
115
|
+
* into view. `canonicalPath` follows the `@pierre/trees` convention
|
|
116
|
+
* (directories carry a trailing slash, files do not). Must be a
|
|
117
|
+
* non-empty path — use {@link scrollToRoot} for the root gesture.
|
|
118
|
+
*/
|
|
119
|
+
reveal(canonicalPath: string): void;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Ask the tree to return to the workspace root: clear any current
|
|
123
|
+
* selection and scroll back to the top of the tree.
|
|
124
|
+
*/
|
|
125
|
+
scrollToRoot(): void;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Ask the tree to collapse every currently expanded folder.
|
|
129
|
+
*/
|
|
130
|
+
collapseAll(): void;
|
|
86
131
|
}
|
|
87
132
|
|
|
88
133
|
/**
|
|
@@ -100,7 +145,7 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
|
|
|
100
145
|
this._onOpenFile = options.onOpenFile;
|
|
101
146
|
this.id = FILE_BROWSER_ID;
|
|
102
147
|
this.title.icon = xtralabFileBrowserIcon;
|
|
103
|
-
this.title.caption = '
|
|
148
|
+
this.title.caption = 'xtralab File Browser';
|
|
104
149
|
this.addClass(FILE_BROWSER_CSS_CLASS);
|
|
105
150
|
|
|
106
151
|
this._toolbar = new Toolbar();
|
|
@@ -142,6 +187,18 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
|
|
|
142
187
|
return this._pathAdded;
|
|
143
188
|
}
|
|
144
189
|
|
|
190
|
+
get revealRequested(): ISignal<this, string> {
|
|
191
|
+
return this._revealRequested;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
get rootRequested(): ISignal<this, void> {
|
|
195
|
+
return this._rootRequested;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get collapseAllRequested(): ISignal<this, void> {
|
|
199
|
+
return this._collapseAllRequested;
|
|
200
|
+
}
|
|
201
|
+
|
|
145
202
|
/**
|
|
146
203
|
* Update the cached selection. Called from the React tree when the
|
|
147
204
|
* underlying `@pierre/trees` model emits a selection change.
|
|
@@ -159,6 +216,23 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
|
|
|
159
216
|
this._pathAdded.emit(canonicalPath);
|
|
160
217
|
}
|
|
161
218
|
|
|
219
|
+
reveal(canonicalPath: string): void {
|
|
220
|
+
if (canonicalPath.length === 0) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
'XtralabFileBrowser.reveal requires a non-empty canonical path; use scrollToRoot for the workspace root.'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
this._revealRequested.emit(canonicalPath);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
scrollToRoot(): void {
|
|
229
|
+
this._rootRequested.emit();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
collapseAll(): void {
|
|
233
|
+
this._collapseAllRequested.emit();
|
|
234
|
+
}
|
|
235
|
+
|
|
162
236
|
private _contentsManager: Contents.IManager;
|
|
163
237
|
private _docManager: IDocumentManager;
|
|
164
238
|
private _onOpenFile: ((serverPath: string) => void) | undefined;
|
|
@@ -166,6 +240,9 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
|
|
|
166
240
|
private _selectionChanged = new Signal<this, readonly string[]>(this);
|
|
167
241
|
private _refreshRequested = new Signal<this, void>(this);
|
|
168
242
|
private _pathAdded = new Signal<this, string>(this);
|
|
243
|
+
private _revealRequested = new Signal<this, string>(this);
|
|
244
|
+
private _rootRequested = new Signal<this, void>(this);
|
|
245
|
+
private _collapseAllRequested = new Signal<this, void>(this);
|
|
169
246
|
private _toolbar: Toolbar;
|
|
170
247
|
private _content: XtralabFileTreeContent;
|
|
171
248
|
}
|
package/src/git/icons.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { LabIcon } from '@jupyterlab/ui-components';
|
|
|
6
6
|
* recoloring and inverse-color sidebar painting automatically.
|
|
7
7
|
*
|
|
8
8
|
* Source: `git-branch` from SVG Repo (https://www.svgrepo.com/svg/377352/git-branch),
|
|
9
|
-
* part of the same Pixelarticons family as the
|
|
9
|
+
* part of the same Pixelarticons family as the xtralab file browser's `file-tree`
|
|
10
10
|
* icon, so the two sidebar tab glyphs read as a matched set. The original
|
|
11
11
|
* `fill="#000000"` is replaced with `fill="currentColor"` and the root element
|
|
12
12
|
* is given the `jp-icon3` class so the icon picks up JupyterLab's theme color
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
+
import { Poll } from '@lumino/polling';
|
|
3
4
|
import { ISignal } from '@lumino/signaling';
|
|
4
5
|
|
|
5
6
|
import {
|
|
@@ -49,6 +50,14 @@ interface IPanelState {
|
|
|
49
50
|
*/
|
|
50
51
|
const STATUS_POLL_INTERVAL_MS = 5000;
|
|
51
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Upper bound on the poll's exponential backoff when status requests fail
|
|
55
|
+
* repeatedly. Matches the default JupyterLab file browser's `max`: long
|
|
56
|
+
* enough that a server outage stops hammering the API, short enough that
|
|
57
|
+
* a transient failure heals on its own.
|
|
58
|
+
*/
|
|
59
|
+
const STATUS_POLL_MAX_MS = 300_000;
|
|
60
|
+
|
|
52
61
|
/**
|
|
53
62
|
* Top-level React component rendered inside the {@link GitPanel} widget.
|
|
54
63
|
* Owns the polling + refresh wiring around the `/git/status` endpoint and
|
|
@@ -109,27 +118,32 @@ export function GitPanelComponent(props: {
|
|
|
109
118
|
}
|
|
110
119
|
}, [repoPath]);
|
|
111
120
|
|
|
112
|
-
// Initial load + polling.
|
|
113
|
-
// the
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return () => {
|
|
121
|
-
window.clearInterval(interval);
|
|
122
|
-
};
|
|
123
|
-
}, [refresh]);
|
|
124
|
-
|
|
125
|
-
// External refresh triggers (toolbar button, plugin-level events).
|
|
121
|
+
// Initial load + polling. Driven by a Lumino `Poll` so the panel stops
|
|
122
|
+
// hitting the git endpoint while the JupyterLab tab is hidden, and so
|
|
123
|
+
// repeated failures back off exponentially instead of spinning at 5s.
|
|
124
|
+
// External refresh triggers (toolbar button, plugin-level events) call
|
|
125
|
+
// `poll.refresh()` to schedule an immediate tick instead of running
|
|
126
|
+
// `refresh()` directly — that keeps the panel from issuing two parallel
|
|
127
|
+
// status requests when an automatic tick happens to land at the same
|
|
128
|
+
// time as an external trigger.
|
|
126
129
|
React.useEffect(() => {
|
|
130
|
+
const poll = new Poll({
|
|
131
|
+
name: '@xtralab/git:panelStatus',
|
|
132
|
+
factory: () => refresh(),
|
|
133
|
+
frequency: {
|
|
134
|
+
interval: STATUS_POLL_INTERVAL_MS,
|
|
135
|
+
backoff: true,
|
|
136
|
+
max: STATUS_POLL_MAX_MS
|
|
137
|
+
},
|
|
138
|
+
standby: 'when-hidden'
|
|
139
|
+
});
|
|
127
140
|
const handler = (): void => {
|
|
128
|
-
void refresh();
|
|
141
|
+
void poll.refresh();
|
|
129
142
|
};
|
|
130
143
|
refreshSignal.connect(handler);
|
|
131
144
|
return () => {
|
|
132
145
|
refreshSignal.disconnect(handler);
|
|
146
|
+
poll.dispose();
|
|
133
147
|
};
|
|
134
148
|
}, [refresh, refreshSignal]);
|
|
135
149
|
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
2
|
|
|
3
3
|
import fileBrowserPlugin from './fileBrowser';
|
|
4
|
+
import editorBreadcrumbsPlugin from './editorBreadcrumbs';
|
|
5
|
+
import editorIndentPlugin from './editorIndent';
|
|
4
6
|
import gitPlugin from './git';
|
|
5
7
|
import launcherPlugin from './launcher';
|
|
6
8
|
import menusPlugin from './menus';
|
|
@@ -15,6 +17,8 @@ import statusBarPlugin from './statusBar';
|
|
|
15
17
|
* available end up running.
|
|
16
18
|
*/
|
|
17
19
|
const plugins: JupyterFrontEndPlugin<unknown>[] = [
|
|
20
|
+
editorBreadcrumbsPlugin,
|
|
21
|
+
editorIndentPlugin,
|
|
18
22
|
fileBrowserPlugin,
|
|
19
23
|
gitPlugin,
|
|
20
24
|
launcherPlugin,
|
|
@@ -2,7 +2,15 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import type { CommandRegistry } from '@lumino/commands';
|
|
4
4
|
import type { ReadonlyPartialJSONObject } from '@lumino/coreutils';
|
|
5
|
-
import {
|
|
5
|
+
import { Poll } from '@lumino/polling';
|
|
6
|
+
import {
|
|
7
|
+
LabIcon,
|
|
8
|
+
ReactWidget,
|
|
9
|
+
consoleIcon,
|
|
10
|
+
notebookIcon,
|
|
11
|
+
refreshIcon,
|
|
12
|
+
terminalIcon
|
|
13
|
+
} from '@jupyterlab/ui-components';
|
|
6
14
|
import type { Widget } from '@lumino/widgets';
|
|
7
15
|
|
|
8
16
|
import { expandStatusFiles, status } from '../git/api';
|
|
@@ -83,6 +91,13 @@ interface IGitState {
|
|
|
83
91
|
*/
|
|
84
92
|
const CHANGES_POLL_INTERVAL_MS = 8000;
|
|
85
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Upper bound on the exponential backoff when the status request fails
|
|
96
|
+
* repeatedly. Matches the rest of the plugin's polls so behavior is
|
|
97
|
+
* consistent across the dashboard, the git panel, and the file tree.
|
|
98
|
+
*/
|
|
99
|
+
const CHANGES_POLL_MAX_MS = 300_000;
|
|
100
|
+
|
|
86
101
|
function LauncherDashboardComponent(
|
|
87
102
|
props: ILauncherDashboardOptions
|
|
88
103
|
): React.ReactElement {
|
|
@@ -107,12 +122,23 @@ function LauncherDashboardComponent(
|
|
|
107
122
|
}
|
|
108
123
|
}, [repoPath]);
|
|
109
124
|
|
|
125
|
+
// Driven by a Lumino `Poll` so the dashboard stops hitting the git
|
|
126
|
+
// endpoint while the JupyterLab tab is hidden, and so repeated
|
|
127
|
+
// failures back off exponentially instead of pegging the 8s cadence.
|
|
110
128
|
React.useEffect(() => {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
const poll = new Poll({
|
|
130
|
+
name: '@xtralab/launcher:gitChanges',
|
|
131
|
+
factory: () => refresh(),
|
|
132
|
+
frequency: {
|
|
133
|
+
interval: CHANGES_POLL_INTERVAL_MS,
|
|
134
|
+
backoff: true,
|
|
135
|
+
max: CHANGES_POLL_MAX_MS
|
|
136
|
+
},
|
|
137
|
+
standby: 'when-hidden'
|
|
138
|
+
});
|
|
139
|
+
return () => {
|
|
140
|
+
poll.dispose();
|
|
141
|
+
};
|
|
116
142
|
}, [refresh]);
|
|
117
143
|
|
|
118
144
|
const launch = React.useCallback(
|
|
@@ -133,6 +159,36 @@ function LauncherDashboardComponent(
|
|
|
133
159
|
[commands, cwd, prompt, onAgentLaunch]
|
|
134
160
|
);
|
|
135
161
|
|
|
162
|
+
const launchTerminal = React.useCallback(async () => {
|
|
163
|
+
const result = (await commands.execute('terminal:create-new', {
|
|
164
|
+
cwd
|
|
165
|
+
})) as Widget | undefined;
|
|
166
|
+
if (result) {
|
|
167
|
+
onAgentLaunch(result);
|
|
168
|
+
}
|
|
169
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
170
|
+
|
|
171
|
+
const launchNotebook = React.useCallback(async () => {
|
|
172
|
+
// No `kernelName` — let the notebook extension fall back to the default
|
|
173
|
+
// kernel or prompt the user, matching the stock launcher's notebook
|
|
174
|
+
// tile behavior.
|
|
175
|
+
const result = (await commands.execute('notebook:create-new', {
|
|
176
|
+
cwd
|
|
177
|
+
})) as Widget | undefined;
|
|
178
|
+
if (result) {
|
|
179
|
+
onAgentLaunch(result);
|
|
180
|
+
}
|
|
181
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
182
|
+
|
|
183
|
+
const launchConsole = React.useCallback(async () => {
|
|
184
|
+
const result = (await commands.execute('console:create', {
|
|
185
|
+
cwd
|
|
186
|
+
})) as Widget | undefined;
|
|
187
|
+
if (result) {
|
|
188
|
+
onAgentLaunch(result);
|
|
189
|
+
}
|
|
190
|
+
}, [commands, cwd, onAgentLaunch]);
|
|
191
|
+
|
|
136
192
|
const openDiff = React.useCallback(
|
|
137
193
|
(change: IFileChange) => {
|
|
138
194
|
const args: GitCommandArguments.IOpenDiff = { repoPath, change };
|
|
@@ -152,6 +208,11 @@ function LauncherDashboardComponent(
|
|
|
152
208
|
onPromptChange={setPrompt}
|
|
153
209
|
onLaunch={launch}
|
|
154
210
|
/>
|
|
211
|
+
<OpenSection
|
|
212
|
+
onLaunchTerminal={launchTerminal}
|
|
213
|
+
onLaunchNotebook={launchNotebook}
|
|
214
|
+
onLaunchConsole={launchConsole}
|
|
215
|
+
/>
|
|
155
216
|
<ChangesSection
|
|
156
217
|
git={git}
|
|
157
218
|
onOpen={openDiff}
|
|
@@ -278,10 +339,9 @@ function AgentSection(props: {
|
|
|
278
339
|
const { agents, prompt, onPromptChange, onLaunch } = props;
|
|
279
340
|
const trimmed = prompt.trim();
|
|
280
341
|
const promptActive = trimmed.length > 0;
|
|
281
|
-
// The
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
// textarea — picking by the same rule keeps the two behaviors in sync.
|
|
342
|
+
// The first prompt-capable agent is what Cmd/Ctrl+Enter fires; the hint
|
|
343
|
+
// text below the textarea names it so the keyboard shortcut target
|
|
344
|
+
// isn't a mystery.
|
|
285
345
|
const primaryAgent = agents.find(agent => agent.promptArgs !== undefined);
|
|
286
346
|
const hintId = 'jp-xtralab-Launcher-agent-hint';
|
|
287
347
|
|
|
@@ -320,19 +380,14 @@ function AgentSection(props: {
|
|
|
320
380
|
{agents.map(agent => {
|
|
321
381
|
const supportsPrompt = agent.promptArgs !== undefined;
|
|
322
382
|
const disabled = promptActive && !supportsPrompt;
|
|
323
|
-
const isPrimary = agent === primaryAgent;
|
|
324
383
|
const tooltip = disabled
|
|
325
384
|
? `${agent.label} doesn't accept an initial prompt — clear the prompt to launch.`
|
|
326
385
|
: agent.caption;
|
|
327
|
-
const classes = ['jp-xtralab-Launcher-agent'];
|
|
328
|
-
if (isPrimary) {
|
|
329
|
-
classes.push('jp-xtralab-Launcher-agent-primary');
|
|
330
|
-
}
|
|
331
386
|
return (
|
|
332
387
|
<button
|
|
333
388
|
key={agent.id}
|
|
334
389
|
type="button"
|
|
335
|
-
className=
|
|
390
|
+
className="jp-xtralab-Launcher-agent"
|
|
336
391
|
title={tooltip}
|
|
337
392
|
aria-label={agent.label}
|
|
338
393
|
aria-describedby={hintId}
|
|
@@ -354,3 +409,67 @@ function AgentSection(props: {
|
|
|
354
409
|
</section>
|
|
355
410
|
);
|
|
356
411
|
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The non-agent launch tiles — a plain terminal, a new notebook, and a
|
|
415
|
+
* new console. They sit in a separate section below the agent grid so
|
|
416
|
+
* they don't compete with the AI agents visually, and they're always
|
|
417
|
+
* available regardless of the agent prompt textarea (which doesn't
|
|
418
|
+
* apply to them).
|
|
419
|
+
*/
|
|
420
|
+
function OpenSection(props: {
|
|
421
|
+
onLaunchTerminal: () => void;
|
|
422
|
+
onLaunchNotebook: () => void;
|
|
423
|
+
onLaunchConsole: () => void;
|
|
424
|
+
}): React.ReactElement {
|
|
425
|
+
const { onLaunchTerminal, onLaunchNotebook, onLaunchConsole } = props;
|
|
426
|
+
return (
|
|
427
|
+
<section className="jp-xtralab-Launcher-section">
|
|
428
|
+
<h2 className="jp-xtralab-Launcher-section-title">Open</h2>
|
|
429
|
+
<div className="jp-xtralab-Launcher-agents">
|
|
430
|
+
<button
|
|
431
|
+
type="button"
|
|
432
|
+
className="jp-xtralab-Launcher-agent"
|
|
433
|
+
title="Open a new terminal."
|
|
434
|
+
aria-label="Open a new terminal"
|
|
435
|
+
onClick={onLaunchTerminal}
|
|
436
|
+
>
|
|
437
|
+
<LabIcon.resolveReact
|
|
438
|
+
icon={terminalIcon}
|
|
439
|
+
tag="span"
|
|
440
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
441
|
+
/>
|
|
442
|
+
<span className="jp-xtralab-Launcher-agent-label">Terminal</span>
|
|
443
|
+
</button>
|
|
444
|
+
<button
|
|
445
|
+
type="button"
|
|
446
|
+
className="jp-xtralab-Launcher-agent"
|
|
447
|
+
title="Create a new notebook."
|
|
448
|
+
aria-label="Create a new notebook"
|
|
449
|
+
onClick={onLaunchNotebook}
|
|
450
|
+
>
|
|
451
|
+
<LabIcon.resolveReact
|
|
452
|
+
icon={notebookIcon}
|
|
453
|
+
tag="span"
|
|
454
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
455
|
+
/>
|
|
456
|
+
<span className="jp-xtralab-Launcher-agent-label">Notebook</span>
|
|
457
|
+
</button>
|
|
458
|
+
<button
|
|
459
|
+
type="button"
|
|
460
|
+
className="jp-xtralab-Launcher-agent"
|
|
461
|
+
title="Create a new console."
|
|
462
|
+
aria-label="Create a new console"
|
|
463
|
+
onClick={onLaunchConsole}
|
|
464
|
+
>
|
|
465
|
+
<LabIcon.resolveReact
|
|
466
|
+
icon={consoleIcon}
|
|
467
|
+
tag="span"
|
|
468
|
+
className="jp-xtralab-Launcher-agent-icon"
|
|
469
|
+
/>
|
|
470
|
+
<span className="jp-xtralab-Launcher-agent-label">Console</span>
|
|
471
|
+
</button>
|
|
472
|
+
</div>
|
|
473
|
+
</section>
|
|
474
|
+
);
|
|
475
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
.jp-xtralab-EditorBreadcrumbs.jp-Toolbar-item {
|
|
2
|
+
flex: 1 1 auto;
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
align-self: center;
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
min-width: 0;
|
|
8
|
+
height: 28px;
|
|
9
|
+
min-height: 28px;
|
|
10
|
+
max-height: 28px;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0 10px;
|
|
13
|
+
color: var(--jp-ui-font-color2);
|
|
14
|
+
font-family: var(--jp-ui-font-family);
|
|
15
|
+
font-size: var(--jp-ui-font-size1);
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-container {
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
min-width: 0;
|
|
23
|
+
max-width: 100%;
|
|
24
|
+
height: 100%;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-content {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
min-width: 0;
|
|
31
|
+
max-width: 100%;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-home {
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
flex: 0 0 auto;
|
|
40
|
+
margin-right: 2px;
|
|
41
|
+
padding: 2px;
|
|
42
|
+
border-radius: var(--jp-border-radius);
|
|
43
|
+
cursor: pointer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-home:hover {
|
|
47
|
+
background-color: var(--jp-layout-color2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-home:focus-visible {
|
|
51
|
+
outline: 2px solid var(--jp-brand-color1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-item {
|
|
55
|
+
flex: 0 1 auto;
|
|
56
|
+
min-width: 0;
|
|
57
|
+
margin: 0 2px;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
padding: 2px;
|
|
60
|
+
border-radius: var(--jp-border-radius);
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
line-height: 18px;
|
|
63
|
+
text-overflow: ellipsis;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-item,
|
|
67
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-separator {
|
|
68
|
+
vertical-align: middle;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-item:hover {
|
|
72
|
+
background-color: var(--jp-layout-color2);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-item:focus-visible {
|
|
76
|
+
outline: 2px solid var(--jp-brand-color1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-item.jp-mod-current {
|
|
80
|
+
color: var(--jp-ui-font-color1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.jp-xtralab-EditorBreadcrumbs .jp-BreadCrumbs-separator {
|
|
84
|
+
flex: 0 0 auto;
|
|
85
|
+
line-height: 18px;
|
|
86
|
+
}
|
package/style/index.css
CHANGED
package/style/index.js
CHANGED
package/style/launcher.css
CHANGED
|
@@ -295,11 +295,6 @@
|
|
|
295
295
|
cursor: not-allowed;
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
.jp-xtralab-Launcher-agent-primary {
|
|
299
|
-
border-color: var(--jp-brand-color2);
|
|
300
|
-
box-shadow: 0 0 0 1px var(--jp-brand-color2);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
298
|
.jp-xtralab-Launcher-agent:hover:not(:disabled) {
|
|
304
299
|
background-color: var(--jp-layout-color2);
|
|
305
300
|
border-color: var(--jp-border-color1);
|
package/style/tabs.css
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Adaptive (VS Code-style) document tab widths. Tabs size to their filename
|
|
3
|
+
* instead of being pinned to --jp-private-horizontal-tab-width (216px), and
|
|
4
|
+
* the horizontal tab strip becomes scrollable when tabs overflow.
|
|
5
|
+
*
|
|
6
|
+
* Mostly mirrors jupyterlab/jupyterlab#18796, with one deliberate divergence:
|
|
7
|
+
* .lm-TabBar-content uses `flex: 0 1 auto` (not `1 1 auto`) so the +/add
|
|
8
|
+
* button sits next to the last tab instead of being pushed to the right
|
|
9
|
+
* edge of the bar. Overflow scrolling still works because `flex-shrink: 1`
|
|
10
|
+
* plus `min-width: 0` lets content shrink when tabs exceed the bar width.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.lm-DockPanel-tabBar .lm-TabBar-tab,
|
|
14
|
+
.lm-TabPanel-tabBar .lm-TabBar-tab {
|
|
15
|
+
flex: 0 0 auto;
|
|
16
|
+
padding: 0 8px;
|
|
17
|
+
cursor: pointer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.lm-DockPanel-tabBar .lm-TabBar-addButton,
|
|
21
|
+
.lm-TabPanel-tabBar .lm-TabBar-addButton {
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.lm-DockPanel-tabBar .lm-TabBar-tab {
|
|
26
|
+
width: auto;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.lm-DockPanel-tabBar .lm-TabBar-content {
|
|
30
|
+
flex: 0 1 auto;
|
|
31
|
+
min-width: 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.lm-DockPanel-tabBar[data-orientation='horizontal'] {
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.lm-DockPanel-tabBar[data-orientation='horizontal'] > .lm-TabBar-content {
|
|
39
|
+
overflow: auto hidden;
|
|
40
|
+
position: relative;
|
|
41
|
+
z-index: 3;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.lm-TabBar-content::-webkit-scrollbar {
|
|
45
|
+
height: 2px;
|
|
46
|
+
background: var(--jp-layout-color1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.lm-TabBar-content::-webkit-scrollbar-button {
|
|
50
|
+
display: none;
|
|
51
|
+
width: 0;
|
|
52
|
+
height: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.lm-TabBar-content::-webkit-scrollbar-track {
|
|
56
|
+
background: var(--jp-layout-color1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.lm-TabBar-content::-webkit-scrollbar-thumb {
|
|
60
|
+
background: rgb(120 120 120 / 40%);
|
|
61
|
+
border-radius: 999px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.lm-TabBar-content:hover::-webkit-scrollbar-thumb {
|
|
65
|
+
background: rgb(160 160 160 / 60%);
|
|
66
|
+
}
|