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
@@ -1,10 +1,21 @@
1
1
  import * as React from 'react';
2
2
 
3
+ import {
4
+ IMovableSectionDestination,
5
+ IMovableSectionSource,
6
+ ISectionEntry
7
+ } from '@jupyterlab/apputils';
3
8
  import { IDocumentManager } from '@jupyterlab/docmanager';
4
9
  import { Contents } from '@jupyterlab/services';
5
- import { ReactWidget, Toolbar } from '@jupyterlab/ui-components';
10
+ import { ITranslator } from '@jupyterlab/translation';
11
+ import {
12
+ AccordionToolbar,
13
+ PanelWithToolbar,
14
+ ReactWidget,
15
+ Toolbar
16
+ } from '@jupyterlab/ui-components';
6
17
  import { Signal, ISignal } from '@lumino/signaling';
7
- import { PanelLayout, Widget } from '@lumino/widgets';
18
+ import { AccordionPanel, PanelLayout, Widget } from '@lumino/widgets';
8
19
 
9
20
  import { FileBrowserComponent } from './fileBrowser';
10
21
  import { xtralabFileBrowserIcon } from './icons';
@@ -12,20 +23,46 @@ import { xtralabFileBrowserIcon } from './icons';
12
23
  export const FILE_BROWSER_ID = 'xtralab:file-browser';
13
24
 
14
25
  /**
15
- * The CSS class added to the xtralab file browser widget. The selectors that
16
- * bind application context menu items hang off this class, so it must remain
17
- * specific enough not to clash with other browsers (such as the default
18
- * `jp-FileBrowser`).
26
+ * Persisted by the movable-sections plugin, so it must stay stable.
27
+ */
28
+ const TREE_SECTION_ID = 'xtralab-file-tree-section';
29
+
30
+ /**
31
+ * Context menu selectors hang off this class, so it sits on the section and
32
+ * keeps matching once the tree moves to another panel.
19
33
  */
20
34
  const FILE_BROWSER_CSS_CLASS = 'jp-xtralab-FileBrowser';
21
35
 
36
+ const PANEL_CSS_CLASS = 'jp-xtralab-FileBrowserPanel';
37
+
22
38
  const TOOLBAR_CSS_CLASS = 'jp-xtralab-FileBrowser-toolbar';
23
39
  const CONTENT_CSS_CLASS = 'jp-xtralab-FileBrowser-content';
24
40
 
41
+ const ACCORDION_CSS_CLASS = 'jp-xtralab-FileBrowser-accordion';
42
+
43
+ const MOVABLE_SECTION_CLASS = 'jp-movable-section';
44
+
45
+ /**
46
+ * The options used to create an {@link XtralabFileBrowser}.
47
+ */
25
48
  interface IXtralabFileBrowserOptions {
49
+ /**
50
+ * The contents manager backing the tree.
51
+ */
26
52
  contentsManager: Contents.IManager;
53
+ /**
54
+ * The document manager used to open files from the tree.
55
+ */
27
56
  docManager: IDocumentManager;
57
+ /**
58
+ * Callback invoked with a file's server path when its row is
59
+ * double-clicked. No double-click handler is installed when absent.
60
+ */
28
61
  onOpenFile?: (serverPath: string) => void;
62
+ /**
63
+ * The application language translator.
64
+ */
65
+ translator?: ITranslator;
29
66
  }
30
67
 
31
68
  /**
@@ -35,82 +72,62 @@ interface IXtralabFileBrowserOptions {
35
72
  */
36
73
  export interface IXtralabFileBrowser {
37
74
  /**
38
- * The contents manager backing the tree. Exposed so command handlers can
39
- * issue contents operations (rename, delete, copy, …) against the same
40
- * drive the tree was loaded from.
75
+ * The contents manager backing the tree; command handlers issue rename,
76
+ * delete, copy, … against the same drive.
41
77
  */
42
78
  readonly contentsManager: Contents.IManager;
43
79
 
44
80
  /**
45
- * The toolbar shown above the tree. Plugin code populates it with command
46
- * buttons after the widget is constructed.
81
+ * The toolbar above the tree, populated by plugin code after construction.
47
82
  */
48
83
  readonly toolbar: Toolbar;
49
84
 
50
85
  /**
51
- * Canonical paths of the items currently selected in the tree. Folder
52
- * paths carry a trailing slash; file paths do not.
86
+ * Canonical selected paths; folder paths carry a trailing slash.
53
87
  */
54
88
  readonly selectedPaths: readonly string[];
55
89
 
56
90
  /**
57
- * Emits when the tree's selection changes. Commands that have an
58
- * `isVisible` / `isEnabled` predicate should listen so they re-evaluate.
91
+ * Emits when the tree's selection changes.
59
92
  */
60
93
  readonly selectionChanged: ISignal<IXtralabFileBrowser, readonly string[]>;
61
94
 
62
95
  /**
63
- * Emits when {@link refresh} is called. The React component listens and
64
- * re-fetches the directories that were previously loaded so the tree
65
- * matches the contents on disk again.
96
+ * Emits when {@link refresh} is called.
66
97
  */
67
98
  readonly refreshRequested: ISignal<IXtralabFileBrowser, void>;
68
99
 
69
100
  /**
70
- * Emits when the widget is asked to surface a newly-created path
71
- * (for example, after the toolbar's "new folder" command). The React
72
- * component listens and inserts the path into the model so the user
73
- * sees the new item without a full refresh.
101
+ * Emits a newly-created canonical path so the tree inserts it without a
102
+ * full refresh.
74
103
  */
75
104
  readonly pathAdded: ISignal<IXtralabFileBrowser, string>;
76
105
 
77
106
  /**
78
- * Emits when an external caller asks the tree to scroll to and select
79
- * the given canonical path. The React component listens, lazily loads
80
- * any unloaded ancestor directories, expands them, and selects the
81
- * target. Used by the editor breadcrumbs to jump back to a file or
82
- * folder shown in the breadcrumb trail.
107
+ * Emits a canonical path to load, expand, select, and scroll to. Used by
108
+ * the editor breadcrumbs.
83
109
  */
84
110
  readonly revealRequested: ISignal<IXtralabFileBrowser, string>;
85
111
 
86
112
  /**
87
- * Emits when an external caller asks the tree to return to the
88
- * workspace root: clear any current selection and scroll back to the
89
- * first row. Distinct from {@link revealRequested} because the
90
- * workspace root has no tree row of its own — it cannot be reached
91
- * by passing a path.
113
+ * Emits when the tree should return to the workspace root — the root has
114
+ * no tree row of its own, so it cannot be revealed by path.
92
115
  */
93
116
  readonly rootRequested: ISignal<IXtralabFileBrowser, void>;
94
117
 
95
118
  /**
96
- * Emits when an external caller asks the tree to collapse every
97
- * expanded folder. The React component listens and walks the loaded
98
- * directories, calling `.collapse()` on each expanded one.
119
+ * Emits when every expanded folder should collapse.
99
120
  */
100
121
  readonly collapseAllRequested: ISignal<IXtralabFileBrowser, void>;
101
122
 
102
123
  /**
103
- * Whether the filter box above the tree is shown. Mirrors the default
104
- * file browser's `showFileFilter`: hidden on startup and flipped by the
105
- * toolbar toggle. Also forced to `true` when the tree opens a search
106
- * session on its own — typing a letter while the tree has focus.
124
+ * Whether the filter box above the tree is shown; also forced `true` when
125
+ * the tree opens a search session itself (typing while it has focus).
107
126
  */
108
127
  readonly fileFilterVisible: boolean;
109
128
 
110
129
  /**
111
- * Emits when {@link fileFilterVisible} changes. The React component
112
- * listens to show or hide the filter box; the toggle command listens
113
- * to refresh its toggled state.
130
+ * Emits when {@link fileFilterVisible} changes.
114
131
  */
115
132
  readonly fileFilterVisibleChanged: ISignal<IXtralabFileBrowser, boolean>;
116
133
 
@@ -120,24 +137,19 @@ export interface IXtralabFileBrowser {
120
137
  refresh(): void;
121
138
 
122
139
  /**
123
- * Notify the React component that a new path was created and should
124
- * appear in the tree. `canonicalPath` follows the `@pierre/trees`
125
- * convention: directories carry a trailing slash, files do not.
140
+ * Notify the React component that a new canonical path was created and
141
+ * should appear in the tree.
126
142
  */
127
143
  notifyPathAdded(canonicalPath: string): void;
128
144
 
129
145
  /**
130
- * Ask the tree to reveal {@link canonicalPath}: load and expand any
131
- * unloaded ancestor directories, then select and scroll the target
132
- * into view. `canonicalPath` follows the `@pierre/trees` convention
133
- * (directories carry a trailing slash, files do not). Must be a
134
- * non-empty path — use {@link scrollToRoot} for the root gesture.
146
+ * Reveal a non-empty canonical path: load and expand ancestors, then
147
+ * select and scroll to it. Use {@link scrollToRoot} for the root gesture.
135
148
  */
136
149
  reveal(canonicalPath: string): void;
137
150
 
138
151
  /**
139
- * Ask the tree to return to the workspace root: clear any current
140
- * selection and scroll back to the top of the tree.
152
+ * Return to the workspace root: clear the selection and scroll to the top.
141
153
  */
142
154
  scrollToRoot(): void;
143
155
 
@@ -155,102 +167,165 @@ export interface IXtralabFileBrowser {
155
167
  * Toggle the filter box above the tree.
156
168
  */
157
169
  toggleFileFilter(): void;
170
+
171
+ /**
172
+ * The DOM node of the movable "Files" section, used to locate the panel
173
+ * currently hosting the tree.
174
+ */
175
+ readonly sectionNode: HTMLElement;
158
176
  }
159
177
 
160
178
  /**
161
- * Lumino widget that hosts the React-based `@pierre/trees` file browser.
162
- * The widget is laid out with a JupyterLab `Toolbar` on top and the tree
163
- * filling the remaining space.
179
+ * Lumino widget hosting the React-based `@pierre/trees` file browser. The tree
180
+ * and its toolbar form one movable "Files" section, and sections from other
181
+ * panels can be moved in below it.
164
182
  */
165
- export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
183
+ export class XtralabFileBrowser
184
+ extends Widget
185
+ implements
186
+ IXtralabFileBrowser,
187
+ IMovableSectionSource,
188
+ IMovableSectionDestination
189
+ {
166
190
  constructor(options: IXtralabFileBrowserOptions) {
167
191
  super();
168
- const layout = (this.layout = new PanelLayout());
192
+ const layout = (this._panelLayout = this.layout = new PanelLayout());
169
193
 
170
194
  this._contentsManager = options.contentsManager;
171
195
  this._docManager = options.docManager;
172
196
  this._onOpenFile = options.onOpenFile;
197
+ this._translator = options.translator;
173
198
  this.id = FILE_BROWSER_ID;
174
199
  this.title.icon = xtralabFileBrowserIcon;
175
200
  this.title.caption = 'xtralab File Browser';
176
- this.addClass(FILE_BROWSER_CSS_CLASS);
201
+ this.addClass(PANEL_CSS_CLASS);
177
202
 
178
203
  this._toolbar = new Toolbar();
179
204
  this._toolbar.addClass(TOOLBAR_CSS_CLASS);
180
205
  this._toolbar.node.setAttribute('aria-label', 'file browser');
181
- layout.addWidget(this._toolbar);
182
206
 
183
207
  this._content = new XtralabFileTreeContent({
184
208
  contentsManager: this._contentsManager,
185
209
  docManager: this._docManager,
186
210
  onOpenFile: this._onOpenFile,
211
+ translator: this._translator,
187
212
  browser: this
188
213
  });
189
214
  this._content.addClass(CONTENT_CSS_CLASS);
190
- layout.addWidget(this._content);
215
+
216
+ this._section = new PanelWithToolbar({ toolbar: this._toolbar });
217
+ this._section.id = TREE_SECTION_ID;
218
+ // Only an accordion host draws this; a plain `PanelLayout` shows no title.
219
+ this._section.title.label = 'Files';
220
+ this._section.addClass(FILE_BROWSER_CSS_CLASS);
221
+ this._section.addWidget(this._toolbar);
222
+ this._section.addWidget(this._content);
223
+ layout.addWidget(this._section);
191
224
  }
192
225
 
226
+ /**
227
+ * The contents manager backing the tree.
228
+ */
193
229
  get contentsManager(): Contents.IManager {
194
230
  return this._contentsManager;
195
231
  }
196
232
 
233
+ /**
234
+ * The toolbar above the tree.
235
+ */
197
236
  get toolbar(): Toolbar {
198
237
  return this._toolbar;
199
238
  }
200
239
 
240
+ /**
241
+ * Canonical selected paths; folder paths carry a trailing slash.
242
+ */
201
243
  get selectedPaths(): readonly string[] {
202
244
  return this._selectedPaths;
203
245
  }
204
246
 
247
+ /**
248
+ * A signal emitted when the tree's selection changes.
249
+ */
205
250
  get selectionChanged(): ISignal<this, readonly string[]> {
206
251
  return this._selectionChanged;
207
252
  }
208
253
 
254
+ /**
255
+ * A signal emitted when {@link refresh} is called.
256
+ */
209
257
  get refreshRequested(): ISignal<this, void> {
210
258
  return this._refreshRequested;
211
259
  }
212
260
 
261
+ /**
262
+ * A signal emitting a newly-created canonical path for the tree to insert.
263
+ */
213
264
  get pathAdded(): ISignal<this, string> {
214
265
  return this._pathAdded;
215
266
  }
216
267
 
268
+ /**
269
+ * A signal emitting a canonical path to load, expand, select, and scroll to.
270
+ */
217
271
  get revealRequested(): ISignal<this, string> {
218
272
  return this._revealRequested;
219
273
  }
220
274
 
275
+ /**
276
+ * A signal emitted when the tree should return to the workspace root.
277
+ */
221
278
  get rootRequested(): ISignal<this, void> {
222
279
  return this._rootRequested;
223
280
  }
224
281
 
282
+ /**
283
+ * A signal emitted when every expanded folder should collapse.
284
+ */
225
285
  get collapseAllRequested(): ISignal<this, void> {
226
286
  return this._collapseAllRequested;
227
287
  }
228
288
 
289
+ /**
290
+ * Whether the filter box above the tree is shown.
291
+ */
229
292
  get fileFilterVisible(): boolean {
230
293
  return this._fileFilterVisible;
231
294
  }
232
295
 
296
+ /**
297
+ * A signal emitted when {@link fileFilterVisible} changes.
298
+ */
233
299
  get fileFilterVisibleChanged(): ISignal<this, boolean> {
234
300
  return this._fileFilterVisibleChanged;
235
301
  }
236
302
 
237
303
  /**
238
- * Update the cached selection. Called from the React tree when the
239
- * underlying `@pierre/trees` model emits a selection change.
304
+ * Update the cached selection from the React tree.
240
305
  */
241
306
  updateSelection(paths: readonly string[]): void {
242
307
  this._selectedPaths = paths;
243
308
  this._selectionChanged.emit(paths);
244
309
  }
245
310
 
311
+ /**
312
+ * Trigger a refresh of every loaded directory in the tree.
313
+ */
246
314
  refresh(): void {
247
315
  this._refreshRequested.emit();
248
316
  }
249
317
 
318
+ /**
319
+ * Notify the tree that a new canonical path was created.
320
+ */
250
321
  notifyPathAdded(canonicalPath: string): void {
251
322
  this._pathAdded.emit(canonicalPath);
252
323
  }
253
324
 
325
+ /**
326
+ * Reveal a non-empty canonical path in the tree; use {@link scrollToRoot}
327
+ * for the workspace root, which has no tree row of its own.
328
+ */
254
329
  reveal(canonicalPath: string): void {
255
330
  if (canonicalPath.length === 0) {
256
331
  throw new Error(
@@ -260,14 +335,23 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
260
335
  this._revealRequested.emit(canonicalPath);
261
336
  }
262
337
 
338
+ /**
339
+ * Return to the workspace root: clear the selection and scroll to the top.
340
+ */
263
341
  scrollToRoot(): void {
264
342
  this._rootRequested.emit();
265
343
  }
266
344
 
345
+ /**
346
+ * Ask the tree to collapse every currently expanded folder.
347
+ */
267
348
  collapseAll(): void {
268
349
  this._collapseAllRequested.emit();
269
350
  }
270
351
 
352
+ /**
353
+ * Show or hide the filter box above the tree.
354
+ */
271
355
  setFileFilterVisible(visible: boolean): void {
272
356
  if (this._fileFilterVisible === visible) {
273
357
  return;
@@ -276,13 +360,181 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
276
360
  this._fileFilterVisibleChanged.emit(visible);
277
361
  }
278
362
 
363
+ /**
364
+ * Toggle the filter box above the tree.
365
+ */
279
366
  toggleFileFilter(): void {
280
367
  this.setFileFilterVisible(!this._fileFilterVisible);
281
368
  }
282
369
 
370
+ /**
371
+ * The DOM node of the movable "Files" section.
372
+ */
373
+ get sectionNode(): HTMLElement {
374
+ return this._section.node;
375
+ }
376
+
377
+ /**
378
+ * Whether the panel currently holds no sections, its own tree included.
379
+ */
380
+ get isEmpty(): boolean {
381
+ return this._panelLayout.widgets.length === 0;
382
+ }
383
+
384
+ /**
385
+ * A signal emitted when a section is added to or removed from the panel.
386
+ */
387
+ get contentChanged(): ISignal<this, void> {
388
+ return this._contentChanged;
389
+ }
390
+
391
+ /**
392
+ * A signal emitting each section entry announced to the move plugin.
393
+ */
394
+ get sectionAdded(): ISignal<this, ISectionEntry> {
395
+ return this._sectionAdded;
396
+ }
397
+
398
+ /**
399
+ * The accordion hosting the sections, or `null` while the tree sits alone.
400
+ */
401
+ get accordionPanel(): AccordionPanel | null {
402
+ return this._accordion;
403
+ }
404
+
405
+ /**
406
+ * Section widgets hosted from other panels; the tree section is excluded.
407
+ */
408
+ get sections(): ReadonlyArray<Widget> {
409
+ if (!this._accordion) {
410
+ return [];
411
+ }
412
+ return this._accordion.widgets.filter(w => w !== this._section);
413
+ }
414
+
415
+ /**
416
+ * The toolbar is the section's title node: it is the tree's header row at
417
+ * home, and an accordion host renders it inside the section title.
418
+ */
419
+ getSections(): ReadonlyArray<ISectionEntry> {
420
+ return this._isSectionHome()
421
+ ? [
422
+ {
423
+ id: TREE_SECTION_ID,
424
+ titleNode: this._toolbar.node,
425
+ widget: this._section
426
+ }
427
+ ]
428
+ : [];
429
+ }
430
+
431
+ /**
432
+ * Detach and return the tree section so the move plugin can re-host it;
433
+ * `null` for any other id or when the section is already away from home.
434
+ */
435
+ removeSectionById(sectionId: string): Widget | null {
436
+ if (sectionId !== TREE_SECTION_ID || !this._isSectionHome()) {
437
+ return null;
438
+ }
439
+ this._section.parent = null;
440
+ if (this._accordion && this._accordion.widgets.length === 0) {
441
+ this._accordion.dispose();
442
+ this._accordion = null;
443
+ }
444
+ // Away from home the toolbar must not offer "Move to …" as well;
445
+ // `getSections` re-marks it on the way back.
446
+ this._toolbar.node.classList.remove(MOVABLE_SECTION_CLASS);
447
+ this._contentChanged.emit();
448
+ return this._section;
449
+ }
450
+
451
+ /**
452
+ * Re-attach the tree section after a move back to this panel.
453
+ */
454
+ reinsertSection(widget: Widget): void {
455
+ if (this._accordion) {
456
+ this._accordion.insertWidget(0, widget);
457
+ } else {
458
+ this._panelLayout.addWidget(widget);
459
+ this._restoreToolbar();
460
+ }
461
+ this._contentChanged.emit();
462
+ }
463
+
464
+ /**
465
+ * The move plugin reconciles persisted moves only against sections announced
466
+ * after `registerSource`, and this panel builds its section in the constructor.
467
+ */
468
+ announceSections(): void {
469
+ for (const entry of this.getSections()) {
470
+ this._sectionAdded.emit(entry);
471
+ }
472
+ }
473
+
474
+ /**
475
+ * The first hosted section moves the tree into an accordion.
476
+ */
477
+ addSection(widget: Widget): void {
478
+ if (!this._accordion) {
479
+ const accordion = new AccordionPanel({
480
+ layout: AccordionToolbar.createLayout({})
481
+ });
482
+ accordion.addClass(ACCORDION_CSS_CLASS);
483
+ if (this._section.parent === this) {
484
+ accordion.addWidget(this._section);
485
+ }
486
+ accordion.addWidget(widget);
487
+ this._accordion = accordion;
488
+ this._panelLayout.addWidget(accordion);
489
+ } else {
490
+ this._accordion.addWidget(widget);
491
+ }
492
+ this._contentChanged.emit();
493
+ }
494
+
495
+ /**
496
+ * Remove a hosted section; the accordion is torn down once only the
497
+ * panel's own tree section (or nothing) remains.
498
+ */
499
+ removeSectionWidget(widget: Widget): void {
500
+ if (!this._accordion || widget.parent !== this._accordion) {
501
+ return;
502
+ }
503
+ widget.parent = null;
504
+ const remaining = this._accordion.widgets;
505
+ const treeOnly = remaining.length === 1 && remaining[0] === this._section;
506
+ if (treeOnly || remaining.length === 0) {
507
+ if (treeOnly) {
508
+ if (this._section.isHidden) {
509
+ this._section.show();
510
+ }
511
+ this._panelLayout.addWidget(this._section);
512
+ this._restoreToolbar();
513
+ }
514
+ this._accordion.dispose();
515
+ this._accordion = null;
516
+ }
517
+ this._contentChanged.emit();
518
+ }
519
+
520
+ private _isSectionHome(): boolean {
521
+ const parent = this._section.parent;
522
+ return parent === this || (!!this._accordion && parent === this._accordion);
523
+ }
524
+
525
+ /**
526
+ * An accordion host moves the toolbar node into the section title and leaves
527
+ * it orphaned on detach; re-parenting makes the layout attach it again.
528
+ */
529
+ private _restoreToolbar(): void {
530
+ this._toolbar.parent = null;
531
+ this._section.insertWidget(0, this._toolbar);
532
+ }
533
+
283
534
  private _contentsManager: Contents.IManager;
284
535
  private _docManager: IDocumentManager;
285
536
  private _onOpenFile: ((serverPath: string) => void) | undefined;
537
+ private _translator: ITranslator | undefined;
286
538
  private _selectedPaths: readonly string[] = [];
287
539
  private _selectionChanged = new Signal<this, readonly string[]>(this);
288
540
  private _refreshRequested = new Signal<this, void>(this);
@@ -294,18 +546,42 @@ export class XtralabFileBrowser extends Widget implements IXtralabFileBrowser {
294
546
  private _fileFilterVisibleChanged = new Signal<this, boolean>(this);
295
547
  private _toolbar: Toolbar;
296
548
  private _content: XtralabFileTreeContent;
549
+ private _section: PanelWithToolbar;
550
+ private _panelLayout: PanelLayout;
551
+ private _accordion: AccordionPanel | null = null;
552
+ private _contentChanged = new Signal<this, void>(this);
553
+ private _sectionAdded = new Signal<this, ISectionEntry>(this);
297
554
  }
298
555
 
556
+ /**
557
+ * The options used to create an {@link XtralabFileTreeContent}.
558
+ */
299
559
  interface IFileTreeContentOptions {
560
+ /**
561
+ * The contents manager backing the tree.
562
+ */
300
563
  contentsManager: Contents.IManager;
564
+ /**
565
+ * The document manager used to open files from the tree.
566
+ */
301
567
  docManager: IDocumentManager;
568
+ /**
569
+ * Callback invoked with a file's server path when its row is
570
+ * double-clicked. No double-click handler is installed when absent.
571
+ */
302
572
  onOpenFile?: (serverPath: string) => void;
573
+ /**
574
+ * The application language translator.
575
+ */
576
+ translator?: ITranslator;
577
+ /**
578
+ * The host widget the React tree reports selection and state changes to.
579
+ */
303
580
  browser: XtralabFileBrowser;
304
581
  }
305
582
 
306
583
  /**
307
- * The React-rendered region of the file browser. Lives inside the host
308
- * widget below the toolbar and renders the actual tree.
584
+ * The React-rendered region below the toolbar that hosts the actual tree.
309
585
  */
310
586
  class XtralabFileTreeContent extends ReactWidget {
311
587
  constructor(options: IFileTreeContentOptions) {
@@ -313,12 +589,16 @@ class XtralabFileTreeContent extends ReactWidget {
313
589
  this._options = options;
314
590
  }
315
591
 
592
+ /**
593
+ * Render the file browser React component.
594
+ */
316
595
  protected render(): React.ReactElement {
317
596
  return (
318
597
  <FileBrowserComponent
319
598
  contentsManager={this._options.contentsManager}
320
599
  docManager={this._options.docManager}
321
600
  onOpenFile={this._options.onOpenFile}
601
+ translator={this._options.translator}
322
602
  widget={this._options.browser}
323
603
  />
324
604
  );
@@ -25,13 +25,9 @@ function representativePath(
25
25
  if (extension === undefined || extension === '') {
26
26
  return null;
27
27
  }
28
- // Extensions are stored with a leading dot (e.g. `.py`, `.tar.gz`).
29
28
  return `file${extension.startsWith('.') ? extension : `.${extension}`}`;
30
29
  }
31
30
 
32
- /**
33
- * Point a file type's icon at the tree glyph for its extension.
34
- */
35
31
  function applyTreeIcon(fileType: DocumentRegistry.IFileType): void {
36
32
  const path = representativePath(fileType);
37
33
  if (path === null) {
@@ -79,9 +75,6 @@ const EXTRA_FILE_TYPE_EXTENSIONS = [
79
75
  '.wasm'
80
76
  ];
81
77
 
82
- /**
83
- * Register icon-only file types for unclaimed source extensions.
84
- */
85
78
  function registerExtraFileTypes(docRegistry: DocumentRegistry): void {
86
79
  for (const extension of EXTRA_FILE_TYPE_EXTENSIONS) {
87
80
  const icon = getSpecificTreeIcon(`file${extension}`);
@@ -125,7 +118,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
125
118
  applyTreeIcon(fileType);
126
119
  }
127
120
  registerExtraFileTypes(docRegistry);
128
- // Pick up file types registered after this plugin activates.
129
121
  docRegistry.changed.connect((_, args) => {
130
122
  if (
131
123
  args.type === 'fileType' &&
@@ -148,8 +140,6 @@ const plugin: JupyterFrontEndPlugin<void> = {
148
140
  }
149
141
  };
150
142
  setIcon();
151
- // Keep the tab icon right after a rename, and release the listener
152
- // when the document closes.
153
143
  widget.context.pathChanged.connect(setIcon);
154
144
  widget.disposed.connect(() => {
155
145
  widget.context.pathChanged.disconnect(setIcon);