xtralab 0.15.0 → 0.15.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/README.md +65 -30
- package/lib/about/icons.d.ts +0 -5
- package/lib/about/icons.js +0 -5
- package/lib/about/index.d.ts +4 -8
- package/lib/about/index.js +6 -17
- package/lib/agentSessions.d.ts +9 -29
- package/lib/agentSessions.js +2 -7
- package/lib/askAgent/editorSelection.d.ts +20 -26
- package/lib/askAgent/editorSelection.js +11 -26
- package/lib/askAgent/icons.d.ts +2 -3
- package/lib/askAgent/icons.js +2 -3
- package/lib/askAgent/index.d.ts +4 -26
- package/lib/askAgent/index.js +54 -158
- package/lib/askAgent/popup.d.ts +12 -16
- package/lib/askAgent/popup.js +18 -37
- package/lib/askAgent/prompt.d.ts +3 -5
- package/lib/askAgent/prompt.js +6 -19
- package/lib/askAgent/queue.d.ts +11 -21
- package/lib/askAgent/queue.js +9 -19
- package/lib/askAgent/queuePanel.d.ts +13 -18
- package/lib/askAgent/queuePanel.js +15 -37
- package/lib/askAgent/targetPicker.d.ts +2 -9
- package/lib/askAgent/targetPicker.js +6 -11
- package/lib/askAgent/tokens.d.ts +32 -46
- package/lib/askAgent/tokens.js +4 -6
- package/lib/commandBar/index.d.ts +4 -13
- package/lib/commandBar/index.js +21 -39
- package/lib/customPanel/index.js +15 -0
- package/lib/editorBreadcrumbs/index.d.ts +3 -6
- package/lib/editorBreadcrumbs/index.js +10 -6
- package/lib/editorBreadcrumbs/widget.d.ts +17 -4
- package/lib/editorBreadcrumbs/widget.js +11 -17
- package/lib/editorIndent/index.d.ts +4 -12
- package/lib/editorIndent/index.js +8 -23
- package/lib/fileBrowser/commands.d.ts +29 -19
- package/lib/fileBrowser/commands.js +83 -97
- package/lib/fileBrowser/contents.d.ts +3 -0
- package/lib/fileBrowser/dragAndDrop.d.ts +17 -4
- package/lib/fileBrowser/dragAndDrop.js +2 -7
- package/lib/fileBrowser/fileBrowser.d.ts +19 -9
- package/lib/fileBrowser/fileBrowser.js +70 -222
- package/lib/fileBrowser/gitStatus.d.ts +16 -10
- package/lib/fileBrowser/gitStatus.js +31 -30
- package/lib/fileBrowser/gitignore.d.ts +4 -17
- package/lib/fileBrowser/gitignore.js +4 -25
- package/lib/fileBrowser/icons.d.ts +7 -17
- package/lib/fileBrowser/icons.js +25 -80
- package/lib/fileBrowser/index.js +2 -4
- package/lib/fileBrowser/widget.d.ts +141 -48
- package/lib/fileBrowser/widget.js +102 -4
- package/lib/fileTypeIcons/index.js +0 -10
- package/lib/git/api.d.ts +5 -13
- package/lib/git/api.js +10 -36
- package/lib/git/askRequest.d.ts +4 -8
- package/lib/git/askRequest.js +6 -20
- package/lib/git/commands.d.ts +45 -5
- package/lib/git/commands.js +20 -13
- package/lib/git/diffModel.js +4 -0
- package/lib/git/diffPreferences.d.ts +90 -0
- package/lib/git/diffPreferences.js +107 -0
- package/lib/git/diffProvider.js +10 -2
- package/lib/git/diffSurface.d.ts +64 -63
- package/lib/git/diffSurface.js +72 -213
- package/lib/git/diffTheme.d.ts +7 -0
- package/lib/git/diffTheme.js +23 -0
- package/lib/git/diffWidget.d.ts +70 -14
- package/lib/git/diffWidget.js +100 -94
- package/lib/git/imageDiff.d.ts +14 -6
- package/lib/git/imageDiff.js +19 -75
- package/lib/git/index.d.ts +0 -3
- package/lib/git/index.js +44 -10
- package/lib/git/notebookDiff.d.ts +110 -33
- package/lib/git/notebookDiff.js +55 -197
- package/lib/git/tokens.d.ts +73 -31
- package/lib/git/tokens.js +2 -3
- package/lib/highlight/index.d.ts +4 -10
- package/lib/highlight/index.js +10 -41
- package/lib/index.d.ts +0 -9
- package/lib/index.js +0 -9
- package/lib/launcher/agents.d.ts +55 -51
- package/lib/launcher/agents.js +15 -57
- package/lib/launcher/availability.d.ts +3 -9
- package/lib/launcher/availability.js +3 -9
- package/lib/launcher/commands.d.ts +7 -32
- package/lib/launcher/commands.js +14 -45
- package/lib/launcher/dashboard.d.ts +19 -24
- package/lib/launcher/dashboard.js +21 -67
- package/lib/launcher/editorRegistry.d.ts +10 -22
- package/lib/launcher/editorRegistry.js +14 -18
- package/lib/launcher/editors.d.ts +39 -45
- package/lib/launcher/editors.js +8 -34
- package/lib/launcher/icons.d.ts +0 -12
- package/lib/launcher/icons.js +5 -39
- package/lib/launcher/index.js +12 -55
- package/lib/launcher/invocation.d.ts +4 -10
- package/lib/launcher/invocation.js +7 -22
- package/lib/launcher/registry.d.ts +9 -7
- package/lib/launcher/registry.js +9 -7
- package/lib/launcher/tokens.d.ts +10 -22
- package/lib/launcher/tokens.js +5 -8
- package/lib/menuBar/index.d.ts +3 -10
- package/lib/menuBar/index.js +11 -28
- package/lib/menus/index.d.ts +3 -9
- package/lib/menus/index.js +14 -30
- package/lib/omnibox/files.d.ts +2 -4
- package/lib/omnibox/files.js +5 -11
- package/lib/omnibox/index.d.ts +4 -14
- package/lib/omnibox/index.js +9 -27
- package/lib/omnibox/model.d.ts +38 -10
- package/lib/omnibox/model.js +15 -33
- package/lib/omnibox/recents.d.ts +15 -17
- package/lib/omnibox/recents.js +12 -17
- package/lib/omnibox/tokens.d.ts +5 -10
- package/lib/omnibox/tokens.js +2 -6
- package/lib/omnibox/widget.d.ts +11 -7
- package/lib/omnibox/widget.js +8 -16
- package/lib/searchReplace/index.d.ts +4 -20
- package/lib/searchReplace/index.js +7 -27
- package/lib/showOutput/index.d.ts +3 -12
- package/lib/showOutput/index.js +6 -22
- package/lib/sidebar/index.d.ts +4 -7
- package/lib/sidebar/index.js +5 -12
- package/lib/terminalNotifications/index.d.ts +4 -10
- package/lib/terminalNotifications/index.js +6 -20
- package/lib/terminals/agentTerminals.d.ts +12 -5
- package/lib/terminals/agentTerminals.js +25 -44
- package/lib/terminals/detection.d.ts +4 -8
- package/lib/terminals/detection.js +4 -8
- package/lib/terminals/index.d.ts +0 -35
- package/lib/terminals/index.js +12 -101
- package/lib/terminals/model.d.ts +64 -139
- package/lib/terminals/model.js +80 -220
- package/lib/terminals/tokens.d.ts +11 -22
- package/lib/terminals/tokens.js +2 -2
- package/lib/terminals/widget.d.ts +43 -13
- package/lib/terminals/widget.js +33 -18
- package/lib/topBar/icons.d.ts +3 -8
- package/lib/topBar/icons.js +3 -8
- package/lib/topBar/index.d.ts +4 -11
- package/lib/topBar/index.js +10 -38
- package/lib/walkthrough/index.d.ts +4 -9
- package/lib/walkthrough/index.js +6 -20
- package/lib/walkthrough/panel.d.ts +40 -9
- package/lib/walkthrough/panel.js +4 -10
- package/package.json +5 -3
- package/schema/git-diff-preferences.json +42 -0
- package/src/about/icons.ts +0 -5
- package/src/about/index.tsx +6 -17
- package/src/agentSessions.ts +9 -29
- package/src/askAgent/editorSelection.ts +22 -29
- package/src/askAgent/icons.ts +2 -3
- package/src/askAgent/index.ts +54 -158
- package/src/askAgent/popup.tsx +23 -45
- package/src/askAgent/prompt.ts +6 -19
- package/src/askAgent/queue.ts +13 -24
- package/src/askAgent/queuePanel.tsx +21 -46
- package/src/askAgent/targetPicker.tsx +5 -15
- package/src/askAgent/tokens.ts +32 -46
- package/src/commandBar/index.ts +24 -39
- package/src/customPanel/index.ts +15 -0
- package/src/editorBreadcrumbs/index.ts +10 -6
- package/src/editorBreadcrumbs/widget.ts +23 -17
- package/src/editorIndent/index.ts +17 -23
- package/src/fileBrowser/commands.ts +119 -96
- package/src/fileBrowser/contents.ts +3 -0
- package/src/fileBrowser/dragAndDrop.ts +17 -7
- package/src/fileBrowser/fileBrowser.tsx +86 -230
- package/src/fileBrowser/gitStatus.ts +36 -33
- package/src/fileBrowser/gitignore.ts +4 -25
- package/src/fileBrowser/icons.ts +25 -80
- package/src/fileBrowser/index.ts +2 -4
- package/src/fileBrowser/widget.tsx +171 -50
- package/src/fileTypeIcons/index.ts +0 -10
- package/src/git/api.ts +10 -36
- package/src/git/askRequest.ts +6 -20
- package/src/git/commands.ts +83 -11
- package/src/git/diffModel.ts +25 -0
- package/src/git/diffPreferences.ts +192 -0
- package/src/git/diffProvider.tsx +11 -2
- package/src/git/diffSurface.tsx +165 -286
- package/src/git/diffTheme.ts +24 -0
- package/src/git/diffWidget.tsx +194 -134
- package/src/git/imageDiff.tsx +56 -84
- package/src/git/index.ts +54 -10
- package/src/git/notebookDiff.tsx +214 -240
- package/src/git/tokens.ts +73 -31
- package/src/highlight/index.ts +10 -41
- package/src/index.ts +0 -9
- package/src/launcher/agents.ts +64 -95
- package/src/launcher/availability.ts +3 -9
- package/src/launcher/commands.ts +14 -51
- package/src/launcher/dashboard.tsx +49 -88
- package/src/launcher/editorRegistry.ts +21 -29
- package/src/launcher/editors.ts +39 -58
- package/src/launcher/icons.ts +5 -39
- package/src/launcher/index.ts +12 -55
- package/src/launcher/invocation.ts +7 -22
- package/src/launcher/registry.ts +9 -7
- package/src/launcher/tokens.ts +10 -22
- package/src/menuBar/index.ts +11 -28
- package/src/menus/index.ts +14 -30
- package/src/omnibox/files.ts +10 -14
- package/src/omnibox/index.ts +9 -27
- package/src/omnibox/model.ts +50 -38
- package/src/omnibox/recents.ts +17 -22
- package/src/omnibox/tokens.ts +5 -10
- package/src/omnibox/widget.tsx +14 -19
- package/src/searchReplace/index.ts +7 -27
- package/src/showOutput/index.ts +6 -22
- package/src/sidebar/index.ts +20 -12
- package/src/terminalNotifications/index.ts +42 -25
- package/src/terminals/agentTerminals.ts +25 -49
- package/src/terminals/detection.ts +4 -8
- package/src/terminals/index.ts +12 -101
- package/src/terminals/model.ts +130 -246
- package/src/terminals/tokens.ts +11 -22
- package/src/terminals/widget.tsx +48 -31
- package/src/topBar/icons.ts +3 -8
- package/src/topBar/index.ts +21 -44
- package/src/walkthrough/index.ts +6 -20
- package/src/walkthrough/panel.ts +40 -10
- package/style/about.css +2 -7
- package/style/askAgent.css +8 -40
- package/style/base.css +9 -45
- package/style/chrome.css +108 -308
- package/style/commandBar.css +4 -20
- package/style/customPanel.css +1 -4
- package/style/git.css +26 -186
- package/style/highlight.css +1 -3
- package/style/launcher.css +6 -70
- package/style/omnibox.css +2 -14
- package/style/showOutput.css +0 -6
- package/style/sidebar.css +29 -9
- package/style/tabs.css +5 -13
- package/style/terminals.css +6 -53
- package/style/topBar.css +2 -22
- package/style/walkthrough.css +0 -8
|
@@ -7,58 +7,26 @@ import { FileTree, useFileTree } from '@pierre/trees/react';
|
|
|
7
7
|
import { ROOT_LOAD_KEY, listDirectory, parentOf, toServerPath } from './contents';
|
|
8
8
|
import { computeDropMoves, computeRootDropMoves, createTreeDragAndDropConfig, useRootDropZone } from './dragAndDrop';
|
|
9
9
|
import { buildIgnoredEntries, loadGitignoreMatcher } from './gitignore';
|
|
10
|
-
import {
|
|
10
|
+
import { GIT_REPO_PATH, loadGitChanges, toGitStatusEntries } from './gitStatus';
|
|
11
11
|
import { FILE_BROWSER_ICONS } from './icons';
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* editor) become visible within this interval without a manual refresh.
|
|
16
|
-
* Aligned with the git panel's own polling so both views update on the
|
|
17
|
-
* same rhythm.
|
|
13
|
+
* Git status poll cadence, aligned with the git panel's polling so both
|
|
14
|
+
* views update on the same rhythm.
|
|
18
15
|
*/
|
|
19
16
|
const GIT_STATUS_POLL_INTERVAL_MS = 5000;
|
|
20
|
-
/**
|
|
21
|
-
* Upper bound on the git status poll's exponential backoff. Matches the
|
|
22
|
-
* other polls in the plugin so behavior is consistent across views.
|
|
23
|
-
*/
|
|
24
17
|
const GIT_STATUS_POLL_MAX_MS = 300000;
|
|
25
18
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* `@jupyterlab/filebrowser`) so the tree picks up files created outside
|
|
29
|
-
* JupyterLab (terminal commands, external editors, `git pull`, …) within
|
|
30
|
-
* the same window as the stock browser.
|
|
19
|
+
* Listing auto-refresh cadence; matches the default file browser's
|
|
20
|
+
* `DEFAULT_REFRESH_INTERVAL` for picking up out-of-band file changes.
|
|
31
21
|
*/
|
|
32
22
|
const FILE_LISTING_REFRESH_INTERVAL_MS = 10000;
|
|
33
|
-
/**
|
|
34
|
-
* Upper bound on the auto-refresh backoff when polls fail repeatedly.
|
|
35
|
-
* Matches the default file browser's `max: 300 * 1000` — five minutes is
|
|
36
|
-
* long enough that a server-side outage stops hammering the API, but
|
|
37
|
-
* short enough that a transient failure heals on its own.
|
|
38
|
-
*/
|
|
39
23
|
const FILE_LISTING_REFRESH_MAX_MS = 300000;
|
|
40
|
-
/**
|
|
41
|
-
* Server-relative repository path used for `/git/*` calls. Empty string
|
|
42
|
-
* means "use the JupyterLab server's root and let git resolve the
|
|
43
|
-
* enclosing repo" — same convention as the git panel and the launcher
|
|
44
|
-
* dashboard.
|
|
45
|
-
*/
|
|
46
|
-
const GIT_REPO_PATH = '';
|
|
47
|
-
/**
|
|
48
|
-
* The custom-element tag used by `@pierre/trees` for its shadow host. Kept
|
|
49
|
-
* as a constant rather than imported so we don't pay the `@pierre/trees`
|
|
50
|
-
* resolution cost just for one string.
|
|
51
|
-
*/
|
|
52
24
|
const FILE_TREE_TAG = 'file-tree-container';
|
|
53
25
|
/**
|
|
54
|
-
* Injected into the tree's shadow root
|
|
55
|
-
* box unless the host carries the
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* outside CSS cannot reach. The third rule restyles the drag-hover row:
|
|
59
|
-
* the library paints it with the selection background, which xtralab
|
|
60
|
-
* maps to a full-strength brand color that is illegible without the
|
|
61
|
-
* inverted selection foreground — hence the quieter ring.
|
|
26
|
+
* Injected into the tree's shadow root, where outside CSS cannot reach.
|
|
27
|
+
* The search box is hidden unless the host carries the filter-bridge marker
|
|
28
|
+
* (the library always renders it), and the drag-hover row gets a quiet ring
|
|
29
|
+
* — the library's selection background is illegible with xtralab's colors.
|
|
62
30
|
*/
|
|
63
31
|
const FILE_TREE_UNSAFE_CSS = '[data-type="item"][data-item-selected="true"] ' +
|
|
64
32
|
'[data-item-section="spacing-item"] {' +
|
|
@@ -73,13 +41,9 @@ const FILE_TREE_UNSAFE_CSS = '[data-type="item"][data-item-selected="true"] ' +
|
|
|
73
41
|
'box-shadow: inset 0 0 0 2px var(--trees-accent);' +
|
|
74
42
|
'}';
|
|
75
43
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* the tree is populated lazily: the root is fetched on mount, and each
|
|
80
|
-
* directory is fetched the first time the user expands it. Expansion is
|
|
81
|
-
* detected by subscribing to the model and diffing against an in-memory load
|
|
82
|
-
* state map.
|
|
44
|
+
* A `@pierre/trees` file tree backed by the Jupyter contents API. The API
|
|
45
|
+
* returns one directory level per request, so directories load lazily on
|
|
46
|
+
* first expand, detected by diffing the model against a load-state map.
|
|
83
47
|
*/
|
|
84
48
|
export function FileBrowserComponent(props) {
|
|
85
49
|
const { contentsManager, docManager, onOpenFile, translator, widget } = props;
|
|
@@ -96,25 +60,16 @@ export function FileBrowserComponent(props) {
|
|
|
96
60
|
});
|
|
97
61
|
React.useEffect(() => {
|
|
98
62
|
const knownDirs = new Map();
|
|
99
|
-
// Mirror of the canonical paths
|
|
100
|
-
//
|
|
101
|
-
// re-test every loaded path against the gitignore matcher whenever
|
|
102
|
-
// either side changes — the model itself does not expose a
|
|
103
|
-
// path-iteration API.
|
|
63
|
+
// Mirror of the loaded canonical paths — the model has no path-iteration
|
|
64
|
+
// API, and the gitignore matcher must re-test every loaded path.
|
|
104
65
|
const loadedPaths = new Set();
|
|
105
66
|
let gitignoreMatcher = null;
|
|
106
67
|
let gitStatusEntries = [];
|
|
107
68
|
let cancelled = false;
|
|
108
69
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* payload, which clears any statuses applied previously.
|
|
113
|
-
*
|
|
114
|
-
* Ignored entries go first so the porcelain entries win in the
|
|
115
|
-
* unlikely event of overlap (a tracked path that also matches a
|
|
116
|
-
* `.gitignore` rule). `@pierre/trees` lets later entries overwrite
|
|
117
|
-
* earlier ones in its internal `statusByPath` map.
|
|
70
|
+
* Push the combined gitignore + porcelain entries into the tree.
|
|
71
|
+
* Ignored entries go first so porcelain entries win on overlap —
|
|
72
|
+
* `@pierre/trees` lets later entries overwrite earlier ones.
|
|
118
73
|
*/
|
|
119
74
|
const syncGitStatus = () => {
|
|
120
75
|
const entries = [];
|
|
@@ -128,12 +83,6 @@ export function FileBrowserComponent(props) {
|
|
|
128
83
|
}
|
|
129
84
|
model.setGitStatus(entries);
|
|
130
85
|
};
|
|
131
|
-
/**
|
|
132
|
-
* Reload the workspace `.gitignore`, then re-apply the resulting
|
|
133
|
-
* ignored statuses. Called on initial mount and whenever the user
|
|
134
|
-
* triggers a refresh — the file may have been edited or created in
|
|
135
|
-
* between.
|
|
136
|
-
*/
|
|
137
86
|
const refreshGitignoreMatcher = async () => {
|
|
138
87
|
let next = null;
|
|
139
88
|
try {
|
|
@@ -148,18 +97,13 @@ export function FileBrowserComponent(props) {
|
|
|
148
97
|
gitignoreMatcher = next;
|
|
149
98
|
syncGitStatus();
|
|
150
99
|
};
|
|
151
|
-
/**
|
|
152
|
-
* Refresh the porcelain-derived git status entries and re-apply them
|
|
153
|
-
* to the tree. Runs on mount, on every refresh, and on a periodic
|
|
154
|
-
* poll so out-of-band changes (terminal `git add`, file edits saved
|
|
155
|
-
* outside the editor, …) become visible without explicit user action.
|
|
156
|
-
*/
|
|
157
100
|
const refreshGitStatus = async () => {
|
|
158
|
-
const
|
|
101
|
+
const changes = await loadGitChanges(GIT_REPO_PATH);
|
|
159
102
|
if (cancelled) {
|
|
160
103
|
return;
|
|
161
104
|
}
|
|
162
|
-
gitStatusEntries =
|
|
105
|
+
gitStatusEntries = toGitStatusEntries(changes);
|
|
106
|
+
widget === null || widget === void 0 ? void 0 : widget.updateGitChanges(changes);
|
|
163
107
|
syncGitStatus();
|
|
164
108
|
};
|
|
165
109
|
const fetchDirectory = async (canonicalPath) => {
|
|
@@ -182,21 +126,16 @@ export function FileBrowserComponent(props) {
|
|
|
182
126
|
}
|
|
183
127
|
}
|
|
184
128
|
else {
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
// entry created out-of-band (e.g. by the "new folder" command's
|
|
188
|
-
// `notifyPathAdded` callback) must be skipped here.
|
|
129
|
+
// The path-store throws when a path is added twice, and entries
|
|
130
|
+
// can arrive out-of-band (e.g. `notifyPathAdded`), so skip those.
|
|
189
131
|
const operations = paths
|
|
190
132
|
.filter(path => model.getItem(path) === null)
|
|
191
133
|
.map(path => ({ type: 'add', path }));
|
|
192
134
|
if (operations.length > 0) {
|
|
193
135
|
model.batch(operations);
|
|
194
136
|
}
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
// the filter are already in the model from a prior add and so
|
|
198
|
-
// belong in the mirror too. `Set.add` is idempotent, so the
|
|
199
|
-
// duplicate adds are a no-op.
|
|
137
|
+
// Paths skipped above are already in the model from a prior add,
|
|
138
|
+
// so they belong in the mirror too.
|
|
200
139
|
for (const path of paths) {
|
|
201
140
|
loadedPaths.add(path);
|
|
202
141
|
}
|
|
@@ -215,10 +154,8 @@ export function FileBrowserComponent(props) {
|
|
|
215
154
|
}
|
|
216
155
|
};
|
|
217
156
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* tree state mirrors what's on disk while preserving the user's
|
|
221
|
-
* expansion state.
|
|
157
|
+
* Re-fetch the root and every previously-expanded directory so the tree
|
|
158
|
+
* mirrors the disk while preserving the user's expansion state.
|
|
222
159
|
*/
|
|
223
160
|
const refreshAll = async () => {
|
|
224
161
|
const expandedPaths = new Set();
|
|
@@ -251,9 +188,8 @@ export function FileBrowserComponent(props) {
|
|
|
251
188
|
return;
|
|
252
189
|
}
|
|
253
190
|
const allPaths = [...rootPaths];
|
|
254
|
-
//
|
|
255
|
-
//
|
|
256
|
-
// entire workspace.
|
|
191
|
+
// Fetch only the previously-expanded subtree so the refresh doesn't
|
|
192
|
+
// walk the entire workspace.
|
|
257
193
|
const subdirsByParent = new Map();
|
|
258
194
|
subdirsByParent.set(ROOT_LOAD_KEY, rootSubdirs);
|
|
259
195
|
const queue = rootSubdirs.filter(s => expandedPaths.has(s));
|
|
@@ -277,8 +213,6 @@ export function FileBrowserComponent(props) {
|
|
|
277
213
|
console.error(`xtralab: refresh failed for "${dir}"`, err);
|
|
278
214
|
}
|
|
279
215
|
}
|
|
280
|
-
// Reset the tree contents and restore the load-state map so future
|
|
281
|
-
// expansions know which directories still need fetching.
|
|
282
216
|
model.resetPaths(allPaths);
|
|
283
217
|
loadedPaths.clear();
|
|
284
218
|
for (const path of allPaths) {
|
|
@@ -290,18 +224,10 @@ export function FileBrowserComponent(props) {
|
|
|
290
224
|
knownDirs.set(subdir, expandedPaths.has(subdir) ? 'loaded' : 'unloaded');
|
|
291
225
|
}
|
|
292
226
|
});
|
|
293
|
-
//
|
|
294
|
-
// since the last load, then re-apply the resulting statuses to the
|
|
295
|
-
// newly-loaded paths. The reload runs in parallel with the rest of
|
|
296
|
-
// the refresh — `refreshGitignoreMatcher` calls `syncGitStatus`
|
|
297
|
-
// itself when it completes. The porcelain status is also re-fetched
|
|
298
|
-
// here so a user-triggered refresh picks up out-of-band git changes
|
|
299
|
-
// immediately instead of waiting for the next poll tick.
|
|
227
|
+
// Each re-applies the statuses itself when it completes.
|
|
300
228
|
void refreshGitignoreMatcher();
|
|
301
229
|
void refreshGitStatus();
|
|
302
|
-
//
|
|
303
|
-
// We have to do this after `resetPaths` because the reset starts
|
|
304
|
-
// every directory in its initial collapsed state.
|
|
230
|
+
// `resetPaths` starts every directory collapsed, so re-expand after it.
|
|
305
231
|
for (const path of expandedPaths) {
|
|
306
232
|
const item = model.getItem(path);
|
|
307
233
|
if (item !== null && item.isDirectory()) {
|
|
@@ -310,20 +236,10 @@ export function FileBrowserComponent(props) {
|
|
|
310
236
|
}
|
|
311
237
|
};
|
|
312
238
|
/**
|
|
313
|
-
* Auto-refresh tick:
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
* state survive every poll. Mirrors what the default JupyterLab file
|
|
318
|
-
* browser does for its single-directory view.
|
|
319
|
-
*
|
|
320
|
-
* A failed fetch for a single directory is treated as transient and
|
|
321
|
-
* is skipped without touching that directory's children — they may
|
|
322
|
-
* still be valid even if this one fetch lost the race with a server
|
|
323
|
-
* restart. A deleted directory eventually surfaces through its
|
|
324
|
-
* parent's diff: when the parent is re-fetched and no longer lists
|
|
325
|
-
* the missing child, the child is removed recursively from the tree
|
|
326
|
-
* and from the load-state tracking maps.
|
|
239
|
+
* Auto-refresh tick: diff every loaded directory and apply batched
|
|
240
|
+
* mutations without `resetPaths`, so expansion, selection, and scroll
|
|
241
|
+
* state survive. A failed fetch is skipped as transient; a deleted
|
|
242
|
+
* directory surfaces through its parent's diff and is removed recursively.
|
|
327
243
|
*/
|
|
328
244
|
const quietRefresh = async () => {
|
|
329
245
|
if (cancelled) {
|
|
@@ -343,10 +259,8 @@ export function FileBrowserComponent(props) {
|
|
|
343
259
|
if (cancelled) {
|
|
344
260
|
return;
|
|
345
261
|
}
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
// can prune deep subtrees, so a path captured at the start of
|
|
349
|
-
// the tick may already be gone.
|
|
262
|
+
// The cascade cleanup below can prune subtrees mid-tick, so a dir
|
|
263
|
+
// captured at the start may already be gone.
|
|
350
264
|
if (knownDirs.get(dir) !== 'loaded') {
|
|
351
265
|
continue;
|
|
352
266
|
}
|
|
@@ -373,8 +287,6 @@ export function FileBrowserComponent(props) {
|
|
|
373
287
|
if (newChildren.has(lp)) {
|
|
374
288
|
continue;
|
|
375
289
|
}
|
|
376
|
-
// Disappeared since the last tick. Remove recursively so any
|
|
377
|
-
// descendants that were also being tracked go with it.
|
|
378
290
|
ops.push({ type: 'remove', path: lp, recursive: true });
|
|
379
291
|
removals.push(lp);
|
|
380
292
|
if (lp.endsWith('/')) {
|
|
@@ -394,15 +306,12 @@ export function FileBrowserComponent(props) {
|
|
|
394
306
|
}
|
|
395
307
|
catch (err) {
|
|
396
308
|
console.error(`xtralab: auto-refresh batch failed for "${dir}"`, err);
|
|
397
|
-
// Leave the
|
|
398
|
-
// sees the same starting state and tries again.
|
|
309
|
+
// Leave the mirrors untouched so the next tick retries.
|
|
399
310
|
continue;
|
|
400
311
|
}
|
|
401
312
|
mutated = true;
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// `recursive: true` removal semantics so descendants we were
|
|
405
|
-
// tracking don't linger in `loadedPaths` or `knownDirs`.
|
|
313
|
+
// Mirror updates only after the batch lands; the cascade below
|
|
314
|
+
// mirrors the `recursive: true` removal semantics.
|
|
406
315
|
for (const r of removals) {
|
|
407
316
|
loadedPaths.delete(r);
|
|
408
317
|
}
|
|
@@ -431,8 +340,7 @@ export function FileBrowserComponent(props) {
|
|
|
431
340
|
loadedPaths.add(a);
|
|
432
341
|
}
|
|
433
342
|
}
|
|
434
|
-
//
|
|
435
|
-
// triggers a fetch instead of being ignored.
|
|
343
|
+
// Untracked subdirectories would ignore their first expand.
|
|
436
344
|
for (const subdir of fetched.subdirectories) {
|
|
437
345
|
if (!knownDirs.has(subdir)) {
|
|
438
346
|
knownDirs.set(subdir, 'unloaded');
|
|
@@ -444,24 +352,8 @@ export function FileBrowserComponent(props) {
|
|
|
444
352
|
}
|
|
445
353
|
};
|
|
446
354
|
/**
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
* scrolled into view. Tolerant of partially-loaded state so the
|
|
450
|
-
* editor breadcrumbs can call it on any path at any time without
|
|
451
|
-
* caring about what the tree has already fetched.
|
|
452
|
-
*
|
|
453
|
-
* Each ancestor is awaited *before* it is expanded — expanding a
|
|
454
|
-
* directory triggers the model's subscribe callback, which
|
|
455
|
-
* synchronously sets the directory's load state to `loading` and
|
|
456
|
-
* kicks off its own fetch in parallel. Awaiting that in-flight
|
|
457
|
-
* fetch would return immediately on the second await, leaving the
|
|
458
|
-
* children unloaded when we move to the next iteration.
|
|
459
|
-
*/
|
|
460
|
-
/**
|
|
461
|
-
* Clear the tree selection and scroll back to the top. Invoked by
|
|
462
|
-
* the file browser widget's `scrollToRoot` method when the home
|
|
463
|
-
* crumb is clicked; gives that gesture visible feedback even when
|
|
464
|
-
* the sidebar is already focused on the file browser.
|
|
355
|
+
* Clear the selection and scroll to the top; gives the home-crumb
|
|
356
|
+
* gesture visible feedback even when the sidebar is already focused.
|
|
465
357
|
*/
|
|
466
358
|
const goToRoot = () => {
|
|
467
359
|
var _a;
|
|
@@ -476,13 +368,6 @@ export function FileBrowserComponent(props) {
|
|
|
476
368
|
container.scrollTop = 0;
|
|
477
369
|
}
|
|
478
370
|
};
|
|
479
|
-
/**
|
|
480
|
-
* Collapse every currently expanded directory in the tree. Walks the
|
|
481
|
-
* `knownDirs` map (the canonical record of which directories have been
|
|
482
|
-
* observed in the tree) and calls `.collapse()` on each loaded
|
|
483
|
-
* directory whose handle reports `isExpanded()`. Unloaded directories
|
|
484
|
-
* are not expanded by definition, so they're skipped.
|
|
485
|
-
*/
|
|
486
371
|
const collapseAll = () => {
|
|
487
372
|
if (cancelled) {
|
|
488
373
|
return;
|
|
@@ -501,6 +386,12 @@ export function FileBrowserComponent(props) {
|
|
|
501
386
|
}
|
|
502
387
|
});
|
|
503
388
|
};
|
|
389
|
+
/**
|
|
390
|
+
* Reveal `canonicalPath`: fetch and expand ancestors, then select and
|
|
391
|
+
* scroll to the target. Each ancestor is fetched *before* expanding —
|
|
392
|
+
* expansion starts the subscribe callback's own fetch, and awaiting that
|
|
393
|
+
* in-flight fetch resolves with the children still unloaded.
|
|
394
|
+
*/
|
|
504
395
|
const revealPath = async (canonicalPath) => {
|
|
505
396
|
if (cancelled || canonicalPath.length === 0) {
|
|
506
397
|
return;
|
|
@@ -511,9 +402,7 @@ export function FileBrowserComponent(props) {
|
|
|
511
402
|
if (segments.length === 0) {
|
|
512
403
|
return;
|
|
513
404
|
}
|
|
514
|
-
//
|
|
515
|
-
// For "foo/bar/baz.txt" → ["foo/", "foo/bar/"].
|
|
516
|
-
// For "foo/bar/" → ["foo/"].
|
|
405
|
+
// "foo/bar/baz.txt" → ["foo/", "foo/bar/"].
|
|
517
406
|
const ancestors = [];
|
|
518
407
|
let cumulative = '';
|
|
519
408
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
@@ -560,15 +449,13 @@ export function FileBrowserComponent(props) {
|
|
|
560
449
|
previous === null || previous === void 0 ? void 0 : previous.deselect();
|
|
561
450
|
}
|
|
562
451
|
target.select();
|
|
563
|
-
// `
|
|
564
|
-
//
|
|
565
|
-
// the rendered window.
|
|
452
|
+
// `scrollToPath` focuses and scrolls even when the row is virtualized
|
|
453
|
+
// out of the rendered window.
|
|
566
454
|
model.scrollToPath(canonicalPath, { focus: true, offset: 'center' });
|
|
567
455
|
};
|
|
568
456
|
/**
|
|
569
|
-
* Insert a newly-created path
|
|
570
|
-
*
|
|
571
|
-
* the parent so the user sees the newly created entry immediately.
|
|
457
|
+
* Insert a newly-created path without a full refresh and expand its
|
|
458
|
+
* parent so the entry is visible immediately.
|
|
572
459
|
*/
|
|
573
460
|
const handlePathAdded = (canonicalPath) => {
|
|
574
461
|
if (model.getItem(canonicalPath) === null) {
|
|
@@ -582,9 +469,8 @@ export function FileBrowserComponent(props) {
|
|
|
582
469
|
}
|
|
583
470
|
loadedPaths.add(canonicalPath);
|
|
584
471
|
if (canonicalPath.endsWith('/') && !knownDirs.has(canonicalPath)) {
|
|
585
|
-
//
|
|
586
|
-
//
|
|
587
|
-
// "unloaded" entry to trigger a fetch on the next expand.
|
|
472
|
+
// A new directory has no children; mark it loaded so a stale
|
|
473
|
+
// "unloaded" entry doesn't trigger a fetch on the next expand.
|
|
588
474
|
knownDirs.set(canonicalPath, 'loaded');
|
|
589
475
|
}
|
|
590
476
|
syncGitStatus();
|
|
@@ -685,10 +571,6 @@ export function FileBrowserComponent(props) {
|
|
|
685
571
|
}
|
|
686
572
|
}
|
|
687
573
|
};
|
|
688
|
-
/**
|
|
689
|
-
* The tree refused the drop and left its model untouched — almost
|
|
690
|
-
* always a name collision at the destination.
|
|
691
|
-
*/
|
|
692
574
|
const failedDrop = (error, context) => {
|
|
693
575
|
if (cancelled) {
|
|
694
576
|
return;
|
|
@@ -744,13 +626,8 @@ export function FileBrowserComponent(props) {
|
|
|
744
626
|
},
|
|
745
627
|
standby: 'when-hidden'
|
|
746
628
|
});
|
|
747
|
-
//
|
|
748
|
-
//
|
|
749
|
-
// outage doesn't hammer the API, and stand by when the tab is
|
|
750
|
-
// hidden so we don't run the polling loop while the user is in
|
|
751
|
-
// another tab. `auto: false` keeps the first tick from racing the
|
|
752
|
-
// initial `fetchDirectory(ROOT_LOAD_KEY)` above — the poll is
|
|
753
|
-
// started explicitly once the initial load is in flight.
|
|
629
|
+
// `auto: false` keeps the first tick from racing the initial root
|
|
630
|
+
// fetch above; the poll is started explicitly below.
|
|
754
631
|
const listingPoll = new Poll({
|
|
755
632
|
auto: false,
|
|
756
633
|
name: '@xtralab/fileBrowser:listing',
|
|
@@ -763,26 +640,16 @@ export function FileBrowserComponent(props) {
|
|
|
763
640
|
standby: 'when-hidden'
|
|
764
641
|
});
|
|
765
642
|
void listingPoll.start();
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
// default file browser uses the same `fileChanged` signal for the
|
|
769
|
-
// same reason. We can't tell from the signal alone whether the
|
|
770
|
-
// change affects a path we're showing, so we just nudge the poll —
|
|
771
|
-
// it diffs the loaded directories and emits no batch ops when
|
|
772
|
-
// nothing relevant changed.
|
|
643
|
+
// In-app contents changes (save, rename, delete) nudge the poll; the
|
|
644
|
+
// signal doesn't say whether a shown path is affected, the diff does.
|
|
773
645
|
const onContentsFileChanged = () => {
|
|
774
646
|
void listingPoll.refresh();
|
|
775
647
|
};
|
|
776
648
|
contentsManager.fileChanged.connect(onContentsFileChanged);
|
|
777
649
|
const unsubscribe = model.subscribe(() => {
|
|
778
|
-
// Search-
|
|
779
|
-
//
|
|
780
|
-
//
|
|
781
|
-
// matching subtree — a single common letter can walk the whole
|
|
782
|
-
// workspace, node_modules included. Closing the search restores
|
|
783
|
-
// the pre-search expansion state, and a directory clicked in the
|
|
784
|
-
// results is toggled after the session closes, so real expansions
|
|
785
|
-
// are still fetched the moment the session ends.
|
|
650
|
+
// Search auto-expands every matching directory — fetching those would
|
|
651
|
+
// walk whole subtrees (node_modules included); real expansions still
|
|
652
|
+
// fetch once the search session closes.
|
|
786
653
|
if (model.isSearchOpen()) {
|
|
787
654
|
return;
|
|
788
655
|
}
|
|
@@ -854,11 +721,8 @@ export function FileBrowserComponent(props) {
|
|
|
854
721
|
}
|
|
855
722
|
};
|
|
856
723
|
}, [model, contentsManager, widget, docManager, trans]);
|
|
857
|
-
// Bridge the tree
|
|
858
|
-
//
|
|
859
|
-
// selection through `getSelectedPaths()` and emits a generic notification
|
|
860
|
-
// through `subscribe`, so we diff against a snapshot to avoid spamming the
|
|
861
|
-
// widget on every unrelated mutation.
|
|
724
|
+
// Bridge the tree selection up to the widget. `subscribe` fires on every
|
|
725
|
+
// mutation, so diff against a snapshot before notifying.
|
|
862
726
|
React.useEffect(() => {
|
|
863
727
|
if (widget === undefined) {
|
|
864
728
|
return;
|
|
@@ -879,13 +743,9 @@ export function FileBrowserComponent(props) {
|
|
|
879
743
|
unsubscribe();
|
|
880
744
|
};
|
|
881
745
|
}, [model, widget]);
|
|
882
|
-
//
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
// (opening focuses the input, closing clears any active filter). The
|
|
886
|
-
// model subscription covers the reverse direction: typing a printable
|
|
887
|
-
// character while the tree has focus opens a session on the tree's own
|
|
888
|
-
// initiative, and the box must surface for that session to be usable.
|
|
746
|
+
// Applying the widget's filter flag stamps the marker the unsafeCSS rule
|
|
747
|
+
// keys on and syncs the search session; the subscription surfaces the box
|
|
748
|
+
// when the tree opens a session itself (typing while focused).
|
|
889
749
|
React.useEffect(() => {
|
|
890
750
|
if (widget === undefined) {
|
|
891
751
|
return;
|
|
@@ -952,21 +812,9 @@ export function FileBrowserComponent(props) {
|
|
|
952
812
|
wrapper.removeEventListener('dblclick', handleDoubleClick);
|
|
953
813
|
};
|
|
954
814
|
}, [onOpenFile]);
|
|
955
|
-
//
|
|
956
|
-
//
|
|
957
|
-
//
|
|
958
|
-
// `<file-tree-container>` custom element. When the user right-clicks a row
|
|
959
|
-
// inside the shadow tree, the event is retargeted to the host element when
|
|
960
|
-
// observed from the light DOM, and `app.contextMenu` walks via
|
|
961
|
-
// `parentElement` — it never enters the shadow tree, so the `[data-type=
|
|
962
|
-
// "item"]` selectors registered in `schema/plugin.json` never match.
|
|
963
|
-
//
|
|
964
|
-
// We listen in the capture phase (so we run before the application's
|
|
965
|
-
// document-level handler) and copy the right-clicked row's data attributes
|
|
966
|
-
// onto the host. Lumino then matches the host as if it were the row, and
|
|
967
|
-
// `app.contextMenuHitTest` from command handlers reads the same attributes
|
|
968
|
-
// back to recover the path. When the right-click misses any row we clear
|
|
969
|
-
// the attributes so empty-area clicks don't show stale per-item entries.
|
|
815
|
+
// Lumino never enters the tree's shadow DOM, so the `[data-type="item"]`
|
|
816
|
+
// selectors can't match rows; mirror the right-clicked row's data
|
|
817
|
+
// attributes onto the host (capture phase, cleared on a miss).
|
|
970
818
|
React.useEffect(() => {
|
|
971
819
|
const wrapper = wrapperRef.current;
|
|
972
820
|
if (wrapper === null) {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import type { GitStatusEntry } from '@pierre/trees';
|
|
2
|
+
import type { IFileChange } from '../git/tokens';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* an empty array when the path is not in a git repo, the request fails, or
|
|
6
|
-
* the server reports a non-zero exit — the file browser must still work
|
|
7
|
-
* without git, so any error here is a no-op rather than a thrown exception.
|
|
8
|
-
*
|
|
9
|
-
* Files that have both a staged and an unstaged change are collapsed into a
|
|
10
|
-
* single entry: the unstaged status wins because that mirrors what is
|
|
11
|
-
* actually different on disk and matches VS Code's tree decoration behavior.
|
|
4
|
+
* Repo path for `/git/*` calls; empty means the server root, letting git
|
|
5
|
+
* resolve the enclosing repo (same convention as the git panel).
|
|
12
6
|
*/
|
|
13
|
-
export declare
|
|
7
|
+
export declare const GIT_REPO_PATH = "";
|
|
8
|
+
/**
|
|
9
|
+
* Fetch the porcelain status for `repoPath`, one entry per staged or
|
|
10
|
+
* unstaged change. Any failure yields an empty array — the file browser
|
|
11
|
+
* must work without git.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadGitChanges(repoPath: string): Promise<IFileChange[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Collapse changes into the tree's `GitStatusEntry` values. A file both
|
|
16
|
+
* staged and unstaged gets one entry, the unstaged status winning (mirrors
|
|
17
|
+
* what is on disk, matching VS Code).
|
|
18
|
+
*/
|
|
19
|
+
export declare function toGitStatusEntries(changes: readonly IFileChange[]): GitStatusEntry[];
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { expandStatusFiles, status } from '../git/api';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
* Repo path for `/git/*` calls; empty means the server root, letting git
|
|
4
|
+
* resolve the enclosing repo (same convention as the git panel).
|
|
5
|
+
*/
|
|
6
|
+
export const GIT_REPO_PATH = '';
|
|
7
|
+
/**
|
|
8
|
+
* Translate a `jupyterlab_git` porcelain status into a `@pierre/trees`
|
|
9
|
+
* `GitStatus`. The tree only supports the six common statuses, so the rare
|
|
10
|
+
* ones (unmerged, typechange, unknown) collapse onto `modified`.
|
|
8
11
|
*/
|
|
9
12
|
function toTreeStatus(value) {
|
|
10
13
|
switch (value) {
|
|
@@ -22,39 +25,37 @@ function toTreeStatus(value) {
|
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
|
-
* Fetch the porcelain status for `repoPath
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* the server reports a non-zero exit — the file browser must still work
|
|
29
|
-
* without git, so any error here is a no-op rather than a thrown exception.
|
|
30
|
-
*
|
|
31
|
-
* Files that have both a staged and an unstaged change are collapsed into a
|
|
32
|
-
* single entry: the unstaged status wins because that mirrors what is
|
|
33
|
-
* actually different on disk and matches VS Code's tree decoration behavior.
|
|
28
|
+
* Fetch the porcelain status for `repoPath`, one entry per staged or
|
|
29
|
+
* unstaged change. Any failure yields an empty array — the file browser
|
|
30
|
+
* must work without git.
|
|
34
31
|
*/
|
|
35
|
-
export async function
|
|
32
|
+
export async function loadGitChanges(repoPath) {
|
|
36
33
|
try {
|
|
37
34
|
const result = await status(repoPath);
|
|
38
35
|
if (result.code !== 0) {
|
|
39
36
|
return [];
|
|
40
37
|
}
|
|
41
|
-
|
|
42
|
-
// Collapse staged + unstaged into one decoration per file. Prefer the
|
|
43
|
-
// unstaged group when both exist; otherwise keep whichever group is
|
|
44
|
-
// present.
|
|
45
|
-
const byPath = new Map();
|
|
46
|
-
for (const change of changes) {
|
|
47
|
-
if (change.group === 'unstaged' || !byPath.has(change.path)) {
|
|
48
|
-
byPath.set(change.path, change.status);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const entries = [];
|
|
52
|
-
byPath.forEach((value, path) => {
|
|
53
|
-
entries.push({ path, status: toTreeStatus(value) });
|
|
54
|
-
});
|
|
55
|
-
return entries;
|
|
38
|
+
return expandStatusFiles(result.files);
|
|
56
39
|
}
|
|
57
40
|
catch (_a) {
|
|
58
41
|
return [];
|
|
59
42
|
}
|
|
60
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Collapse changes into the tree's `GitStatusEntry` values. A file both
|
|
46
|
+
* staged and unstaged gets one entry, the unstaged status winning (mirrors
|
|
47
|
+
* what is on disk, matching VS Code).
|
|
48
|
+
*/
|
|
49
|
+
export function toGitStatusEntries(changes) {
|
|
50
|
+
const byPath = new Map();
|
|
51
|
+
for (const change of changes) {
|
|
52
|
+
if (change.group === 'unstaged' || !byPath.has(change.path)) {
|
|
53
|
+
byPath.set(change.path, change.status);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const entries = [];
|
|
57
|
+
byPath.forEach((value, path) => {
|
|
58
|
+
entries.push({ path, status: toTreeStatus(value) });
|
|
59
|
+
});
|
|
60
|
+
return entries;
|
|
61
|
+
}
|