xtralab 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/README.md +10 -0
  2. package/lib/about/icons.d.ts +0 -5
  3. package/lib/about/icons.js +0 -5
  4. package/lib/about/index.d.ts +4 -8
  5. package/lib/about/index.js +6 -17
  6. package/lib/agentSessions.d.ts +9 -29
  7. package/lib/agentSessions.js +2 -7
  8. package/lib/askAgent/editorSelection.d.ts +20 -26
  9. package/lib/askAgent/editorSelection.js +11 -26
  10. package/lib/askAgent/icons.d.ts +2 -3
  11. package/lib/askAgent/icons.js +2 -3
  12. package/lib/askAgent/index.d.ts +4 -26
  13. package/lib/askAgent/index.js +54 -158
  14. package/lib/askAgent/popup.d.ts +12 -16
  15. package/lib/askAgent/popup.js +18 -37
  16. package/lib/askAgent/prompt.d.ts +3 -5
  17. package/lib/askAgent/prompt.js +6 -19
  18. package/lib/askAgent/queue.d.ts +11 -21
  19. package/lib/askAgent/queue.js +9 -19
  20. package/lib/askAgent/queuePanel.d.ts +13 -18
  21. package/lib/askAgent/queuePanel.js +15 -37
  22. package/lib/askAgent/targetPicker.d.ts +2 -9
  23. package/lib/askAgent/targetPicker.js +6 -11
  24. package/lib/askAgent/tokens.d.ts +32 -46
  25. package/lib/askAgent/tokens.js +4 -6
  26. package/lib/commandBar/index.d.ts +4 -13
  27. package/lib/commandBar/index.js +21 -39
  28. package/lib/customPanel/index.d.ts +7 -0
  29. package/lib/customPanel/index.js +89 -0
  30. package/lib/editorBreadcrumbs/index.d.ts +3 -6
  31. package/lib/editorBreadcrumbs/index.js +10 -6
  32. package/lib/editorBreadcrumbs/widget.d.ts +17 -4
  33. package/lib/editorBreadcrumbs/widget.js +11 -17
  34. package/lib/editorIndent/index.d.ts +4 -12
  35. package/lib/editorIndent/index.js +8 -23
  36. package/lib/fileBrowser/commands.d.ts +28 -19
  37. package/lib/fileBrowser/commands.js +51 -98
  38. package/lib/fileBrowser/contents.d.ts +14 -0
  39. package/lib/fileBrowser/contents.js +28 -0
  40. package/lib/fileBrowser/dragAndDrop.d.ts +79 -0
  41. package/lib/fileBrowser/dragAndDrop.js +110 -0
  42. package/lib/fileBrowser/fileBrowser.d.ts +21 -9
  43. package/lib/fileBrowser/fileBrowser.js +211 -405
  44. package/lib/fileBrowser/gitStatus.d.ts +4 -9
  45. package/lib/fileBrowser/gitStatus.js +7 -17
  46. package/lib/fileBrowser/gitignore.d.ts +4 -17
  47. package/lib/fileBrowser/gitignore.js +4 -25
  48. package/lib/fileBrowser/icons.d.ts +7 -17
  49. package/lib/fileBrowser/icons.js +25 -80
  50. package/lib/fileBrowser/index.js +41 -10
  51. package/lib/fileBrowser/widget.d.ts +170 -53
  52. package/lib/fileBrowser/widget.js +242 -18
  53. package/lib/fileTypeIcons/index.js +0 -10
  54. package/lib/git/api.d.ts +5 -13
  55. package/lib/git/api.js +10 -36
  56. package/lib/git/askRequest.d.ts +4 -8
  57. package/lib/git/askRequest.js +6 -20
  58. package/lib/git/commands.d.ts +40 -5
  59. package/lib/git/commands.js +16 -10
  60. package/lib/git/diffModel.js +4 -0
  61. package/lib/git/diffSurface.d.ts +41 -55
  62. package/lib/git/diffSurface.js +42 -117
  63. package/lib/git/diffWidget.d.ts +81 -2
  64. package/lib/git/diffWidget.js +62 -12
  65. package/lib/git/imageDiff.d.ts +5 -6
  66. package/lib/git/imageDiff.js +18 -48
  67. package/lib/git/index.d.ts +0 -3
  68. package/lib/git/index.js +3 -8
  69. package/lib/git/notebookDiff.d.ts +110 -33
  70. package/lib/git/notebookDiff.js +52 -195
  71. package/lib/git/tokens.d.ts +73 -31
  72. package/lib/git/tokens.js +2 -3
  73. package/lib/highlight/index.d.ts +4 -10
  74. package/lib/highlight/index.js +10 -41
  75. package/lib/index.d.ts +0 -9
  76. package/lib/index.js +2 -9
  77. package/lib/launcher/agents.d.ts +55 -51
  78. package/lib/launcher/agents.js +25 -54
  79. package/lib/launcher/availability.d.ts +3 -9
  80. package/lib/launcher/availability.js +3 -9
  81. package/lib/launcher/commands.d.ts +7 -32
  82. package/lib/launcher/commands.js +14 -45
  83. package/lib/launcher/dashboard.d.ts +19 -24
  84. package/lib/launcher/dashboard.js +21 -67
  85. package/lib/launcher/editorRegistry.d.ts +10 -22
  86. package/lib/launcher/editorRegistry.js +14 -18
  87. package/lib/launcher/editors.d.ts +39 -45
  88. package/lib/launcher/editors.js +8 -34
  89. package/lib/launcher/icons.d.ts +0 -12
  90. package/lib/launcher/icons.js +14 -37
  91. package/lib/launcher/index.js +12 -55
  92. package/lib/launcher/invocation.d.ts +4 -10
  93. package/lib/launcher/invocation.js +7 -22
  94. package/lib/launcher/registry.d.ts +9 -7
  95. package/lib/launcher/registry.js +9 -7
  96. package/lib/launcher/schemaDefaults.d.ts +2 -9
  97. package/lib/launcher/schemaDefaults.js +37 -13
  98. package/lib/launcher/tokens.d.ts +10 -22
  99. package/lib/launcher/tokens.js +5 -8
  100. package/lib/menuBar/index.d.ts +3 -10
  101. package/lib/menuBar/index.js +11 -28
  102. package/lib/menus/index.d.ts +3 -9
  103. package/lib/menus/index.js +14 -30
  104. package/lib/omnibox/files.d.ts +2 -4
  105. package/lib/omnibox/files.js +5 -11
  106. package/lib/omnibox/index.d.ts +4 -14
  107. package/lib/omnibox/index.js +9 -27
  108. package/lib/omnibox/model.d.ts +38 -10
  109. package/lib/omnibox/model.js +15 -33
  110. package/lib/omnibox/recents.d.ts +15 -17
  111. package/lib/omnibox/recents.js +12 -17
  112. package/lib/omnibox/tokens.d.ts +5 -10
  113. package/lib/omnibox/tokens.js +2 -6
  114. package/lib/omnibox/widget.d.ts +11 -7
  115. package/lib/omnibox/widget.js +8 -16
  116. package/lib/searchReplace/index.d.ts +4 -20
  117. package/lib/searchReplace/index.js +7 -27
  118. package/lib/showOutput/index.d.ts +3 -12
  119. package/lib/showOutput/index.js +6 -22
  120. package/lib/sidebar/index.d.ts +4 -13
  121. package/lib/sidebar/index.js +73 -46
  122. package/lib/terminalNotifications/index.d.ts +4 -10
  123. package/lib/terminalNotifications/index.js +6 -20
  124. package/lib/terminals/agentTerminals.d.ts +12 -5
  125. package/lib/terminals/agentTerminals.js +25 -44
  126. package/lib/terminals/detection.d.ts +4 -8
  127. package/lib/terminals/detection.js +4 -8
  128. package/lib/terminals/index.d.ts +0 -33
  129. package/lib/terminals/index.js +24 -102
  130. package/lib/terminals/model.d.ts +64 -139
  131. package/lib/terminals/model.js +80 -220
  132. package/lib/terminals/tokens.d.ts +11 -22
  133. package/lib/terminals/tokens.js +2 -2
  134. package/lib/terminals/widget.d.ts +72 -38
  135. package/lib/terminals/widget.js +173 -73
  136. package/lib/topBar/icons.d.ts +3 -8
  137. package/lib/topBar/icons.js +3 -8
  138. package/lib/topBar/index.d.ts +4 -11
  139. package/lib/topBar/index.js +10 -38
  140. package/lib/walkthrough/index.d.ts +4 -9
  141. package/lib/walkthrough/index.js +6 -20
  142. package/lib/walkthrough/panel.d.ts +40 -9
  143. package/lib/walkthrough/panel.js +4 -10
  144. package/package.json +2 -2
  145. package/schema/launcher.json +2 -2
  146. package/schema/sidebar.json +47 -7
  147. package/src/about/icons.ts +0 -5
  148. package/src/about/index.tsx +6 -17
  149. package/src/agentSessions.ts +9 -29
  150. package/src/askAgent/editorSelection.ts +22 -29
  151. package/src/askAgent/icons.ts +2 -3
  152. package/src/askAgent/index.ts +54 -158
  153. package/src/askAgent/popup.tsx +23 -45
  154. package/src/askAgent/prompt.ts +6 -19
  155. package/src/askAgent/queue.ts +13 -24
  156. package/src/askAgent/queuePanel.tsx +21 -46
  157. package/src/askAgent/targetPicker.tsx +5 -15
  158. package/src/askAgent/tokens.ts +32 -46
  159. package/src/commandBar/index.ts +24 -39
  160. package/src/customPanel/index.ts +119 -0
  161. package/src/editorBreadcrumbs/index.ts +10 -6
  162. package/src/editorBreadcrumbs/widget.ts +23 -17
  163. package/src/editorIndent/index.ts +17 -23
  164. package/src/fileBrowser/commands.ts +70 -98
  165. package/src/fileBrowser/contents.ts +33 -0
  166. package/src/fileBrowser/dragAndDrop.ts +188 -0
  167. package/src/fileBrowser/fileBrowser.tsx +259 -440
  168. package/src/fileBrowser/gitStatus.ts +7 -17
  169. package/src/fileBrowser/gitignore.ts +4 -25
  170. package/src/fileBrowser/icons.ts +25 -80
  171. package/src/fileBrowser/index.ts +43 -10
  172. package/src/fileBrowser/widget.tsx +344 -64
  173. package/src/fileTypeIcons/index.ts +0 -10
  174. package/src/git/api.ts +10 -36
  175. package/src/git/askRequest.ts +6 -20
  176. package/src/git/commands.ts +70 -10
  177. package/src/git/diffModel.ts +25 -0
  178. package/src/git/diffSurface.tsx +71 -159
  179. package/src/git/diffWidget.tsx +104 -14
  180. package/src/git/imageDiff.tsx +45 -51
  181. package/src/git/index.ts +3 -8
  182. package/src/git/notebookDiff.tsx +211 -238
  183. package/src/git/tokens.ts +73 -31
  184. package/src/highlight/index.ts +10 -41
  185. package/src/index.ts +2 -9
  186. package/src/launcher/agents.ts +74 -92
  187. package/src/launcher/availability.ts +3 -9
  188. package/src/launcher/commands.ts +14 -51
  189. package/src/launcher/dashboard.tsx +49 -88
  190. package/src/launcher/editorRegistry.ts +21 -29
  191. package/src/launcher/editors.ts +39 -58
  192. package/src/launcher/icons.ts +15 -37
  193. package/src/launcher/index.ts +12 -55
  194. package/src/launcher/invocation.ts +7 -22
  195. package/src/launcher/registry.ts +9 -7
  196. package/src/launcher/schemaDefaults.ts +43 -13
  197. package/src/launcher/tokens.ts +10 -22
  198. package/src/menuBar/index.ts +11 -28
  199. package/src/menus/index.ts +14 -30
  200. package/src/omnibox/files.ts +10 -14
  201. package/src/omnibox/index.ts +9 -27
  202. package/src/omnibox/model.ts +50 -38
  203. package/src/omnibox/recents.ts +17 -22
  204. package/src/omnibox/tokens.ts +5 -10
  205. package/src/omnibox/widget.tsx +14 -19
  206. package/src/searchReplace/index.ts +7 -27
  207. package/src/showOutput/index.ts +6 -22
  208. package/src/sidebar/index.ts +103 -52
  209. package/src/terminalNotifications/index.ts +42 -25
  210. package/src/terminals/agentTerminals.ts +25 -49
  211. package/src/terminals/detection.ts +4 -8
  212. package/src/terminals/index.ts +31 -102
  213. package/src/terminals/model.ts +130 -246
  214. package/src/terminals/tokens.ts +11 -22
  215. package/src/terminals/widget.tsx +229 -113
  216. package/src/topBar/icons.ts +3 -8
  217. package/src/topBar/index.ts +21 -44
  218. package/src/walkthrough/index.ts +6 -20
  219. package/src/walkthrough/panel.ts +40 -10
  220. package/style/about.css +2 -7
  221. package/style/askAgent.css +8 -40
  222. package/style/base.css +25 -73
  223. package/style/chrome.css +108 -308
  224. package/style/commandBar.css +4 -20
  225. package/style/customPanel.css +6 -0
  226. package/style/git.css +22 -184
  227. package/style/highlight.css +1 -3
  228. package/style/index.js +1 -0
  229. package/style/launcher.css +6 -70
  230. package/style/omnibox.css +2 -14
  231. package/style/showOutput.css +0 -6
  232. package/style/sidebar.css +29 -9
  233. package/style/tabs.css +5 -13
  234. package/style/terminals.css +18 -123
  235. package/style/topBar.css +2 -22
  236. package/style/walkthrough.css +0 -8
@@ -5,11 +5,9 @@ import { addIcon, closeIcon, collapseAllIcon, CommandToolbarButton, copyIcon, do
5
5
  import { toCanonicalPath, toServerPath } from './contents';
6
6
  import { FILE_BROWSER_ID } from './widget';
7
7
  /**
8
- * Command identifiers exposed by the xtralab browser. We deliberately namespace
9
- * these under `xtralab:` rather than reusing the `filebrowser:` ids
10
- * because the core `filebrowser:*` commands look up a `FileBrowser` instance
11
- * via `IFileBrowserFactory.tracker` — our widget is not a `FileBrowser`, so
12
- * those handlers would never see it.
8
+ * Command ids for the xtralab browser. Namespaced `xtralab:` rather than
9
+ * reusing the `filebrowser:` ids: the core handlers resolve their target via
10
+ * `IFileBrowserFactory.tracker`, which never sees this widget.
13
11
  */
14
12
  export var CommandIDs;
15
13
  (function (CommandIDs) {
@@ -29,18 +27,13 @@ export var CommandIDs;
29
27
  CommandIDs.revealInFileTree = 'xtralab:reveal-in-file-tree';
30
28
  })(CommandIDs || (CommandIDs = {}));
31
29
  /**
32
- * Submenu id used to host the dynamically populated "Open With" entries.
33
- * Distinct from the core file browser's `jp-contextmenu-open-with` so the
34
- * core's populator does not try to fill ours with items derived from the
35
- * default file browser's selection.
30
+ * "Open With" submenu id. Distinct from the core `jp-contextmenu-open-with`
31
+ * so the core's populator does not fill ours from its own selection.
36
32
  */
37
33
  const OPEN_WITH_SUBMENU_ID = 'jp-contextmenu-xtralab-open-with';
38
34
  /**
39
- * Resolve the path of the item the user right-clicked. We prefer the
40
- * contextmenu event's target — that is how `app.contextMenu` resolves which
41
- * items match a selector — and fall back to the first item in the tree's
42
- * selection if the command was invoked without an active contextmenu event
43
- * (e.g. from the command palette).
35
+ * Path of the right-clicked item, from the contextmenu event's target;
36
+ * falls back to the tree selection when invoked without one (the palette).
44
37
  */
45
38
  function getTargetPath(app, browser) {
46
39
  const node = app.contextMenuHitTest(n => n.dataset !== undefined && n.dataset.type === 'item');
@@ -50,25 +43,15 @@ function getTargetPath(app, browser) {
50
43
  }
51
44
  return browser.selectedPaths[0];
52
45
  }
53
- /**
54
- * Resolve the kind of the item that was right-clicked, based on the data
55
- * attributes emitted by `@pierre/trees` rows. Returns `undefined` if no
56
- * contextmenu event is active or the target is not a tree item.
57
- */
58
46
  function getTargetKind(app) {
59
47
  const node = app.contextMenuHitTest(n => n.dataset !== undefined && n.dataset.type === 'item');
60
48
  const kind = node === null || node === void 0 ? void 0 : node.dataset.itemType;
61
49
  return kind === 'file' || kind === 'folder' ? kind : undefined;
62
50
  }
63
51
  /**
64
- * Resolve the canonical paths the "Open" command should act on. When the
65
- * user right-clicks a row that is part of the current selection, every
66
- * selected file is opened — matching the default file browser, where
67
- * "Open" on any file in a multi-selection opens them all. If the
68
- * right-clicked row is *not* in the selection, only that row is opened.
69
- *
70
- * Folders are always filtered out: there is no "current directory" in
71
- * this tree, so opening a folder via "Open" is a no-op.
52
+ * Canonical paths "Open" acts on: the whole selection when the right-clicked
53
+ * row is part of it (matching the default browser), else just that row.
54
+ * Folders are filtered out — this tree has no "current directory".
72
55
  */
73
56
  function getOpenPaths(app, browser) {
74
57
  const node = app.contextMenuHitTest(n => n.dataset !== undefined && n.dataset.type === 'item');
@@ -83,18 +66,13 @@ function getOpenPaths(app, browser) {
83
66
  }
84
67
  return candidates.filter(path => !path.endsWith('/'));
85
68
  }
86
- /**
87
- * True iff there is an actionable target for a command on the right-click.
88
- */
89
69
  function hasTarget(app, browser) {
90
70
  return getTargetPath(app, browser) !== undefined;
91
71
  }
92
72
  /**
93
- * Resolve the directory used as the working directory for actions that
94
- * create a new file or folder. Folder selections become their own cwd;
95
- * file selections fall back to their parent. Returns the empty string
96
- * when the tree has no selection — that matches the contents API's
97
- * convention for "the root directory".
73
+ * Working directory for create actions: a selected folder is its own cwd, a
74
+ * file falls back to its parent; empty string (the contents-API root) when
75
+ * nothing is selected.
98
76
  */
99
77
  function getWorkingDirectory(browser) {
100
78
  const first = browser.selectedPaths[0];
@@ -108,14 +86,10 @@ function getWorkingDirectory(browser) {
108
86
  return PathExt.dirname(serverPath);
109
87
  }
110
88
  /**
111
- * Resolve the main-area widget whose tab is the target of the current
112
- * context-menu event, or `null` when that target is not a document tab. The
113
- * shell stamps each tab's `data-id` with its widget id, so the right-clicked
114
- * tab resolves even when it is not the current one.
115
- *
116
- * Valid only during a context-menu invocation: `contextMenuHitTest` reads the
117
- * last context-menu event, which is not cleared when the menu closes, so
118
- * {@link documentPathToReveal} gates this to avoid resolving a stale tab.
89
+ * Main-area widget whose tab is the target of the current context-menu
90
+ * event, or `null`. `contextMenuHitTest` reads the last context-menu event,
91
+ * which is not cleared when the menu closes, so callers must gate against
92
+ * resolving a stale tab.
119
93
  */
120
94
  function contextMenuTabWidget(app) {
121
95
  const node = app.contextMenuHitTest(n => n.dataset !== undefined && n.dataset.type === 'document-title');
@@ -131,10 +105,9 @@ function contextMenuTabWidget(app) {
131
105
  return null;
132
106
  }
133
107
  /**
134
- * Resolve the document to reveal as a canonical `@pierre/trees` path: the
135
- * right-clicked tab when `fromContextMenu` is true (the file-tab menu),
136
- * otherwise the active main-area widget (the palette). A file's context
137
- * `path` is already canonical. Returns `undefined` when nothing resolves.
108
+ * Canonical path of the document to reveal: the right-clicked tab when
109
+ * `fromContextMenu` is true (the file-tab menu), else the active main-area
110
+ * widget (the palette).
138
111
  */
139
112
  function documentPathToReveal(app, docManager, fromContextMenu) {
140
113
  var _a, _b, _c;
@@ -146,14 +119,10 @@ function documentPathToReveal(app, docManager, fromContextMenu) {
146
119
  return path.length > 0 ? path : undefined;
147
120
  }
148
121
  /**
149
- * Refresh the "Open With" submenu's items from the document factories that
150
- * can open the file under the right-click. Mirrors the
151
- * `@jupyterlab/filebrowser-extension:open-with` pattern: the schema declares
152
- * an empty submenu by id, and we look it up on the open context menu and
153
- * fill it before the user can hover.
154
- *
122
+ * Build the populator that fills the "Open With" submenu on every
123
+ * context-menu open, mirroring `filebrowser-extension:open-with`.
155
124
  * `preferredWidgetFactories(path)` is path-only, so the populator stays
156
- * synchronous and the submenu is ready before the user can hover over it.
125
+ * synchronous and the submenu is ready before the user can hover.
157
126
  */
158
127
  function makeOpenWithUpdater(app, browser) {
159
128
  let items = [];
@@ -182,13 +151,9 @@ function makeOpenWithUpdater(app, browser) {
182
151
  };
183
152
  }
184
153
  /**
185
- * Register every xtralab command on the application command registry, attach
186
- * the items to the application context menu, and wire up the dynamic
187
- * "Open With" submenu populator.
188
- *
189
- * @returns A function that detaches the menu items and signal listener.
190
- * The commands themselves are owned by the registry for the lifetime of
191
- * the plugin.
154
+ * Register the xtralab commands, context-menu wiring, and the dynamic
155
+ * "Open With" populator. Returns a detach function; the commands themselves
156
+ * stay owned by the registry.
192
157
  */
193
158
  export function registerCommands(opts) {
194
159
  var _a;
@@ -403,27 +368,32 @@ export function registerCommands(opts) {
403
368
  browser.toggleFileFilter();
404
369
  }
405
370
  });
406
- // Refresh the toggle's state however the filter is shown or hidden —
407
- // the toolbar button, or the auto-show that kicks in when typing with
408
- // the tree focused opens a search session.
371
+ // The filter can also auto-show when typing with the tree focused, so
372
+ // track visibility changes rather than the command's own executions.
409
373
  const onFilterVisibleChanged = () => {
410
374
  commands.notifyCommandChanged(CommandIDs.toggleFileFilter);
411
375
  };
412
376
  browser.fileFilterVisibleChanged.connect(onFilterVisibleChanged);
413
- // Public reveal seam. Other plugins (editor breadcrumbs, future
414
- // "reveal in tree" actions) call this command rather than depending
415
- // on the file browser widget directly. Activating the sidebar is part
416
- // of the contract: the tree is hidden behind a tab and a reveal that
417
- // does not surface the panel would silently no-op from the user's
418
- // point of view. An empty path is the "workspace root" gesture —
419
- // there is no tree row for the root itself, so the browser is asked
420
- // to scroll back to the top and clear its selection instead.
377
+ // Public reveal seam for other plugins; an empty path is the workspace-root
378
+ // gesture (the root has no tree row of its own). The tree is movable, so
379
+ // surface whichever sidebar widget holds it.
380
+ const activateTreeHost = () => {
381
+ for (const area of ['left', 'right']) {
382
+ for (const widget of app.shell.widgets(area)) {
383
+ if (widget.node.contains(browser.sectionNode)) {
384
+ app.shell.activateById(widget.id);
385
+ return;
386
+ }
387
+ }
388
+ }
389
+ app.shell.activateById(FILE_BROWSER_ID);
390
+ };
421
391
  commands.addCommand(CommandIDs.revealPath, {
422
392
  label: trans.__('Reveal in File Browser'),
423
393
  execute: (args) => {
424
394
  var _a;
425
395
  const path = (_a = args.path) !== null && _a !== void 0 ? _a : '';
426
- app.shell.activateById(FILE_BROWSER_ID);
396
+ activateTreeHost();
427
397
  if (path.length === 0) {
428
398
  browser.scrollToRoot();
429
399
  }
@@ -432,8 +402,6 @@ export function registerCommands(opts) {
432
402
  }
433
403
  }
434
404
  });
435
- // Reveal the resolved document in the tree browser via the reveal-path
436
- // command, which surfaces the sidebar and scrolls to the target.
437
405
  commands.addCommand(CommandIDs.revealInFileTree, {
438
406
  label: trans.__('Show in File Tree'),
439
407
  caption: trans.__('Show this file in the xtralab file browser'),
@@ -468,41 +436,29 @@ export function registerCommands(opts) {
468
436
  commands.addCommand(CommandIDs.newLauncher, {
469
437
  label: trans.__('New Launcher'),
470
438
  caption: trans.__('Open a new launcher'),
471
- // Skip the `menuItem` bindprops the other commands use: this command
472
- // lives on the toolbar, where the launcher-extension's blue button
473
- // styling expects the raw `jp-icon3` paths from `addIcon` so it can
474
- // recolor them against the brand background.
439
+ // Raw `addIcon`, no `menuItem` bindprops: the launcher-extension's blue
440
+ // toolbar-button styling recolors the raw `jp-icon3` paths.
475
441
  icon: addIcon,
476
442
  execute: () => {
477
443
  const cwd = getWorkingDirectory(browser);
478
444
  return commands.execute('launcher:create', { cwd });
479
445
  }
480
446
  });
481
- // Re-evaluate enabled/visible state when the user changes the tree
482
- // selection, in case a command's predicate depends on the selection.
483
447
  browser.selectionChanged.connect(() => {
484
448
  for (const id of Object.values(CommandIDs)) {
485
449
  commands.notifyCommandChanged(id);
486
450
  }
487
451
  });
488
- // This command's enabled state tracks the active widget, not the tree
489
- // selection, so re-evaluate it on current-widget changes. `currentChanged`
490
- // is optional on the shell interface.
491
452
  const onCurrentChanged = () => {
492
453
  commands.notifyCommandChanged(CommandIDs.revealInFileTree);
493
454
  };
494
455
  (_a = app.shell.currentChanged) === null || _a === void 0 ? void 0 : _a.connect(onCurrentChanged);
495
- // Also surface it in the palette (acts on the active editor); the file-tab
496
- // context-menu entry is declared in `schema/plugin.json`.
497
456
  const paletteItem = palette === null || palette === void 0 ? void 0 : palette.addItem({
498
457
  command: CommandIDs.revealInFileTree,
499
458
  category: trans.__('File Browser')
500
459
  });
501
- // The static items are declared in `schema/plugin.json` under
502
- // `jupyter.lab.menus.context` so users can override or disable them. The
503
- // schema also declares an empty submenu placeholder with id
504
- // `OPEN_WITH_SUBMENU_ID`; we fill it on every open from the document
505
- // factories that can open the right-clicked file.
460
+ // Static context-menu items live in `schema/plugin.json` so users can
461
+ // override them; the schema declares the empty "Open With" placeholder.
506
462
  const updateOpenWithMenu = makeOpenWithUpdater(app, browser);
507
463
  app.contextMenu.opened.connect(updateOpenWithMenu);
508
464
  return () => {
@@ -514,9 +470,8 @@ export function registerCommands(opts) {
514
470
  };
515
471
  }
516
472
  /**
517
- * The names we register with the toolbar. Kept distinct from
518
- * {@link CommandIDs} because the toolbar API uses opaque names rather
519
- * than commands.
473
+ * Toolbar item names, distinct from {@link CommandIDs}: the toolbar API
474
+ * uses opaque names rather than commands.
520
475
  */
521
476
  export var ToolbarNames;
522
477
  (function (ToolbarNames) {
@@ -527,10 +482,8 @@ export var ToolbarNames;
527
482
  ToolbarNames.toggleFileFilter = 'toggle-file-filter';
528
483
  })(ToolbarNames || (ToolbarNames = {}));
529
484
  /**
530
- * Populate the file browser's toolbar with the buttons that mirror the
531
- * default JupyterLab file browser: a "+" launcher button, a new-folder
532
- * button, a refresh button, a collapse-all button, and the file filter
533
- * toggle.
485
+ * Populate the toolbar with the buttons that mirror the default JupyterLab
486
+ * file browser.
534
487
  */
535
488
  export function populateToolbar(opts) {
536
489
  const { app, browser } = opts;
@@ -10,11 +10,25 @@ export declare const ROOT_LOAD_KEY = "";
10
10
  * still be represented in the flat path list.
11
11
  */
12
12
  export declare function toCanonicalPath(child: Contents.IModel): string;
13
+ /**
14
+ * Compute the canonical parent path for a canonical path. Returns
15
+ * {@link ROOT_LOAD_KEY} for top-level entries.
16
+ */
17
+ export declare function parentOf(canonicalPath: string): string;
18
+ /**
19
+ * The last segment of a canonical path. Directories keep their trailing
20
+ * slash, so appending the result to a canonical directory path yields a
21
+ * canonical path again.
22
+ */
23
+ export declare function canonicalBasename(canonicalPath: string): string;
13
24
  /**
14
25
  * Strip the trailing slash that `@pierre/trees` uses for canonical directory
15
26
  * paths so the value can be sent to the Jupyter contents API.
16
27
  */
17
28
  export declare function toServerPath(canonicalPath: string): string;
29
+ /**
30
+ * The immediate children of a directory, as canonical `@pierre/trees` paths.
31
+ */
18
32
  interface IListedDirectory {
19
33
  /**
20
34
  * Canonical paths for every immediate child of the requested directory.
@@ -12,6 +12,34 @@ export const ROOT_LOAD_KEY = '';
12
12
  export function toCanonicalPath(child) {
13
13
  return child.type === 'directory' ? `${child.path}${DIR_SUFFIX}` : child.path;
14
14
  }
15
+ /**
16
+ * Compute the canonical parent path for a canonical path. Returns
17
+ * {@link ROOT_LOAD_KEY} for top-level entries.
18
+ */
19
+ export function parentOf(canonicalPath) {
20
+ const trimmed = canonicalPath.endsWith(DIR_SUFFIX)
21
+ ? canonicalPath.slice(0, -DIR_SUFFIX.length)
22
+ : canonicalPath;
23
+ const idx = trimmed.lastIndexOf(DIR_SUFFIX);
24
+ if (idx < 0) {
25
+ return ROOT_LOAD_KEY;
26
+ }
27
+ return `${trimmed.slice(0, idx)}${DIR_SUFFIX}`;
28
+ }
29
+ /**
30
+ * The last segment of a canonical path. Directories keep their trailing
31
+ * slash, so appending the result to a canonical directory path yields a
32
+ * canonical path again.
33
+ */
34
+ export function canonicalBasename(canonicalPath) {
35
+ const isDir = canonicalPath.endsWith(DIR_SUFFIX);
36
+ const trimmed = isDir
37
+ ? canonicalPath.slice(0, -DIR_SUFFIX.length)
38
+ : canonicalPath;
39
+ const idx = trimmed.lastIndexOf(DIR_SUFFIX);
40
+ const base = idx < 0 ? trimmed : trimmed.slice(idx + DIR_SUFFIX.length);
41
+ return isDir ? `${base}${DIR_SUFFIX}` : base;
42
+ }
15
43
  /**
16
44
  * Strip the trailing slash that `@pierre/trees` uses for canonical directory
17
45
  * paths so the value can be sent to the Jupyter contents API.
@@ -0,0 +1,79 @@
1
+ import * as React from 'react';
2
+ import type { FileTree, FileTreeDragAndDropConfig, FileTreeDropContext, FileTreeDropResult } from '@pierre/trees';
3
+ /**
4
+ * One tree-model move a drop performs, as canonical paths.
5
+ */
6
+ export interface IDropMove {
7
+ /**
8
+ * The canonical path of the dragged item.
9
+ */
10
+ from: string;
11
+ /**
12
+ * The canonical destination path: its basename under the target directory.
13
+ */
14
+ to: string;
15
+ }
16
+ /**
17
+ * Receives drop outcomes from the `@pierre/trees` drag-and-drop
18
+ * machinery. Implemented inside the file browser's contents-sync
19
+ * effect, which owns the load-state and contents-API bookkeeping.
20
+ */
21
+ export interface ITreeDropHandler {
22
+ /**
23
+ * The tree applied the drop to its model: persist the move and
24
+ * re-key the load-state mirrors.
25
+ */
26
+ completeDrop(result: FileTreeDropResult): void;
27
+ /**
28
+ * The tree refused the drop (typically a name collision) and left
29
+ * its model untouched.
30
+ */
31
+ failedDrop(error: string, context: FileTreeDropContext): void;
32
+ /**
33
+ * A drop on the empty space below the last row: move the dragged
34
+ * paths to the workspace root.
35
+ */
36
+ dropOnRoot(draggedPaths: readonly string[]): void;
37
+ }
38
+ /**
39
+ * The from→to moves a drop performs: each dragged path keeps its
40
+ * basename under the target directory. Paths already directly inside
41
+ * the target are skipped.
42
+ */
43
+ export declare function computeDropMoves(context: FileTreeDropContext): IDropMove[];
44
+ /**
45
+ * The moves for a drop of `paths` onto the workspace root.
46
+ */
47
+ export declare function computeRootDropMoves(paths: readonly string[]): IDropMove[];
48
+ /**
49
+ * Build the `dragAndDrop` option for `useFileTree`. The tree captures
50
+ * its options once, so the callbacks delegate through `handlerRef`,
51
+ * filled by the contents-sync effect. `canDrop` hides the drop
52
+ * highlight for all-no-op drops, which the library would accept.
53
+ */
54
+ export declare function createTreeDragAndDropConfig(handlerRef: React.RefObject<ITreeDropHandler | null>): FileTreeDragAndDropConfig;
55
+ /**
56
+ * Options for {@link useRootDropZone}.
57
+ */
58
+ interface IRootDropZoneOptions {
59
+ /**
60
+ * The tree model, queried for the selection at drag start.
61
+ */
62
+ model: FileTree;
63
+ /**
64
+ * The drop handler ref, filled by the contents-sync effect.
65
+ */
66
+ handlerRef: React.RefObject<ITreeDropHandler | null>;
67
+ /**
68
+ * The light-DOM wrapper around the tree host; composed drag events
69
+ * bubble out of the shadow root to it.
70
+ */
71
+ wrapperRef: React.RefObject<HTMLDivElement | null>;
72
+ }
73
+ /**
74
+ * Accept drops on the empty space below the last row as moves to the
75
+ * workspace root — the tree resolves a drop target only while the cursor
76
+ * is over a row and silently discards such drops.
77
+ */
78
+ export declare function useRootDropZone(options: IRootDropZoneOptions): void;
79
+ export {};
@@ -0,0 +1,110 @@
1
+ import * as React from 'react';
2
+ import { ROOT_LOAD_KEY, canonicalBasename, parentOf } from './contents';
3
+ /**
4
+ * The from→to moves a drop performs: each dragged path keeps its
5
+ * basename under the target directory. Paths already directly inside
6
+ * the target are skipped.
7
+ */
8
+ export function computeDropMoves(context) {
9
+ var _a;
10
+ const dir = (_a = context.target.directoryPath) !== null && _a !== void 0 ? _a : ROOT_LOAD_KEY;
11
+ return context.draggedPaths
12
+ .filter(path => parentOf(path) !== dir)
13
+ .map(path => ({ from: path, to: `${dir}${canonicalBasename(path)}` }));
14
+ }
15
+ const ROOT_DROP_TARGET = {
16
+ directoryPath: null,
17
+ flattenedSegmentPath: null,
18
+ hoveredPath: null,
19
+ kind: 'root'
20
+ };
21
+ /**
22
+ * The moves for a drop of `paths` onto the workspace root.
23
+ */
24
+ export function computeRootDropMoves(paths) {
25
+ return computeDropMoves({ draggedPaths: paths, target: ROOT_DROP_TARGET });
26
+ }
27
+ /**
28
+ * Build the `dragAndDrop` option for `useFileTree`. The tree captures
29
+ * its options once, so the callbacks delegate through `handlerRef`,
30
+ * filled by the contents-sync effect. `canDrop` hides the drop
31
+ * highlight for all-no-op drops, which the library would accept.
32
+ */
33
+ export function createTreeDragAndDropConfig(handlerRef) {
34
+ return {
35
+ canDrop: context => computeDropMoves(context).length > 0,
36
+ onDropComplete: result => {
37
+ var _a;
38
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.completeDrop(result);
39
+ },
40
+ onDropError: (error, context) => {
41
+ var _a;
42
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.failedDrop(error, context);
43
+ }
44
+ };
45
+ }
46
+ /**
47
+ * Accept drops on the empty space below the last row as moves to the
48
+ * workspace root — the tree resolves a drop target only while the cursor
49
+ * is over a row and silently discards such drops.
50
+ */
51
+ export function useRootDropZone(options) {
52
+ const { model, handlerRef, wrapperRef } = options;
53
+ React.useEffect(() => {
54
+ const wrapper = wrapperRef.current;
55
+ if (wrapper === null) {
56
+ return;
57
+ }
58
+ let draggedPaths = null;
59
+ const findRow = (event) => {
60
+ for (const target of event.composedPath()) {
61
+ if (target instanceof HTMLElement && target.dataset.type === 'item') {
62
+ return target;
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+ const handleDragStart = (event) => {
68
+ var _a;
69
+ draggedPaths = null;
70
+ // The tree cancels dragstart when it refuses a session, e.g.
71
+ // while the filter is open.
72
+ if (event.defaultPrevented) {
73
+ return;
74
+ }
75
+ const path = (_a = findRow(event)) === null || _a === void 0 ? void 0 : _a.dataset.itemPath;
76
+ if (path === undefined || path.length === 0) {
77
+ return;
78
+ }
79
+ // The selection is the dragged set (the tree selects the pressed
80
+ // row); entries nested under a dragged folder travel with it.
81
+ const selection = model.getSelectedPaths();
82
+ const candidates = selection.includes(path) ? selection : [path];
83
+ const folders = candidates.filter(c => c.endsWith('/'));
84
+ draggedPaths = candidates.filter(c => !folders.some(folder => folder !== c && c.startsWith(folder)));
85
+ };
86
+ const handleDrop = (event) => {
87
+ var _a;
88
+ const paths = draggedPaths;
89
+ draggedPaths = null;
90
+ // Drops on a row were already resolved by the tree itself.
91
+ if (paths === null || findRow(event) !== null) {
92
+ return;
93
+ }
94
+ event.preventDefault();
95
+ (_a = handlerRef.current) === null || _a === void 0 ? void 0 : _a.dropOnRoot(paths);
96
+ };
97
+ const handleDragEnd = () => {
98
+ draggedPaths = null;
99
+ };
100
+ wrapper.addEventListener('dragstart', handleDragStart);
101
+ wrapper.addEventListener('drop', handleDrop);
102
+ window.addEventListener('dragend', handleDragEnd);
103
+ return () => {
104
+ wrapper.removeEventListener('dragstart', handleDragStart);
105
+ wrapper.removeEventListener('drop', handleDrop);
106
+ window.removeEventListener('dragend', handleDragEnd);
107
+ draggedPaths = null;
108
+ };
109
+ }, [model, handlerRef, wrapperRef]);
110
+ }
@@ -1,25 +1,37 @@
1
1
  import * as React from 'react';
2
2
  import { IDocumentManager } from '@jupyterlab/docmanager';
3
3
  import { Contents } from '@jupyterlab/services';
4
+ import { ITranslator } from '@jupyterlab/translation';
4
5
  import type { XtralabFileBrowser } from './widget';
6
+ /**
7
+ * Props for {@link FileBrowserComponent}.
8
+ */
5
9
  interface IFileBrowserProps {
10
+ /**
11
+ * The Jupyter contents manager backing the tree.
12
+ */
6
13
  contentsManager: Contents.IManager;
14
+ /**
15
+ * The document manager used to rename files on drag-and-drop moves.
16
+ */
7
17
  docManager: IDocumentManager;
18
+ /**
19
+ * Called with the server path of an activated file.
20
+ */
8
21
  onOpenFile?: (serverPath: string) => void;
9
22
  /**
10
- * The host widget. Selection-change events are pushed up so context-menu
11
- * commands can react to what the user has selected.
23
+ * The application translator; defaults to `nullTranslator`.
24
+ */
25
+ translator?: ITranslator;
26
+ /**
27
+ * The host widget; selection changes are pushed up for command handlers.
12
28
  */
13
29
  widget?: XtralabFileBrowser;
14
30
  }
15
31
  /**
16
- * Renders a `@pierre/trees` file tree backed by the Jupyter contents API.
17
- *
18
- * The Jupyter contents API only returns one directory level per request, so
19
- * the tree is populated lazily: the root is fetched on mount, and each
20
- * directory is fetched the first time the user expands it. Expansion is
21
- * detected by subscribing to the model and diffing against an in-memory load
22
- * state map.
32
+ * A `@pierre/trees` file tree backed by the Jupyter contents API. The API
33
+ * returns one directory level per request, so directories load lazily on
34
+ * first expand, detected by diffing the model against a load-state map.
23
35
  */
24
36
  export declare function FileBrowserComponent(props: IFileBrowserProps): React.ReactElement;
25
37
  export {};