xtralab 0.3.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 +2 -0
- 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 +3 -0
- package/lib/fileBrowser/commands.js +40 -1
- package/lib/fileBrowser/fileBrowser.js +393 -10
- package/lib/fileBrowser/gitStatus.d.ts +13 -0
- package/lib/fileBrowser/gitStatus.js +60 -0
- package/lib/fileBrowser/widget.d.ts +48 -0
- package/lib/fileBrowser/widget.js +24 -0
- package/lib/git/panelComponent.js +28 -11
- package/lib/index.js +4 -0
- package/lib/launcher/dashboard.js +27 -15
- package/package.json +7 -1
- 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 +43 -1
- package/src/fileBrowser/fileBrowser.tsx +427 -11
- package/src/fileBrowser/gitStatus.ts +66 -0
- package/src/fileBrowser/widget.tsx +77 -0
- package/src/git/panelComponent.tsx +29 -15
- package/src/index.ts +4 -0
- package/src/launcher/dashboard.tsx +28 -15
- 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
|
@@ -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,6 +2,7 @@ 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 { Poll } from '@lumino/polling';
|
|
5
6
|
import {
|
|
6
7
|
LabIcon,
|
|
7
8
|
ReactWidget,
|
|
@@ -90,6 +91,13 @@ interface IGitState {
|
|
|
90
91
|
*/
|
|
91
92
|
const CHANGES_POLL_INTERVAL_MS = 8000;
|
|
92
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
|
+
|
|
93
101
|
function LauncherDashboardComponent(
|
|
94
102
|
props: ILauncherDashboardOptions
|
|
95
103
|
): React.ReactElement {
|
|
@@ -114,12 +122,23 @@ function LauncherDashboardComponent(
|
|
|
114
122
|
}
|
|
115
123
|
}, [repoPath]);
|
|
116
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.
|
|
117
128
|
React.useEffect(() => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
};
|
|
123
142
|
}, [refresh]);
|
|
124
143
|
|
|
125
144
|
const launch = React.useCallback(
|
|
@@ -320,10 +339,9 @@ function AgentSection(props: {
|
|
|
320
339
|
const { agents, prompt, onPromptChange, onLaunch } = props;
|
|
321
340
|
const trimmed = prompt.trim();
|
|
322
341
|
const promptActive = trimmed.length > 0;
|
|
323
|
-
// The
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
// 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.
|
|
327
345
|
const primaryAgent = agents.find(agent => agent.promptArgs !== undefined);
|
|
328
346
|
const hintId = 'jp-xtralab-Launcher-agent-hint';
|
|
329
347
|
|
|
@@ -362,19 +380,14 @@ function AgentSection(props: {
|
|
|
362
380
|
{agents.map(agent => {
|
|
363
381
|
const supportsPrompt = agent.promptArgs !== undefined;
|
|
364
382
|
const disabled = promptActive && !supportsPrompt;
|
|
365
|
-
const isPrimary = agent === primaryAgent;
|
|
366
383
|
const tooltip = disabled
|
|
367
384
|
? `${agent.label} doesn't accept an initial prompt — clear the prompt to launch.`
|
|
368
385
|
: agent.caption;
|
|
369
|
-
const classes = ['jp-xtralab-Launcher-agent'];
|
|
370
|
-
if (isPrimary) {
|
|
371
|
-
classes.push('jp-xtralab-Launcher-agent-primary');
|
|
372
|
-
}
|
|
373
386
|
return (
|
|
374
387
|
<button
|
|
375
388
|
key={agent.id}
|
|
376
389
|
type="button"
|
|
377
|
-
className=
|
|
390
|
+
className="jp-xtralab-Launcher-agent"
|
|
378
391
|
title={tooltip}
|
|
379
392
|
aria-label={agent.label}
|
|
380
393
|
aria-describedby={hintId}
|
|
@@ -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
|
+
}
|