@jupyterlab/filebrowser 4.0.0-alpha.2 → 4.0.0-alpha.20

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/src/browser.ts ADDED
@@ -0,0 +1,444 @@
1
+ // Copyright (c) Jupyter Development Team.
2
+ // Distributed under the terms of the Modified BSD License.
3
+
4
+ import { showErrorMessage } from '@jupyterlab/apputils';
5
+ import { IDocumentManager } from '@jupyterlab/docmanager';
6
+ import { Contents, ServerConnection } from '@jupyterlab/services';
7
+ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
8
+ import { SidePanel } from '@jupyterlab/ui-components';
9
+ import { Panel } from '@lumino/widgets';
10
+ import { BreadCrumbs } from './crumbs';
11
+ import { DirListing } from './listing';
12
+ import { FilterFileBrowserModel } from './model';
13
+
14
+ /**
15
+ * The class name added to file browsers.
16
+ */
17
+ const FILE_BROWSER_CLASS = 'jp-FileBrowser';
18
+
19
+ /**
20
+ * The class name added to file browser panel (gather filter, breadcrumbs and listing).
21
+ */
22
+ const FILE_BROWSER_PANEL_CLASS = 'jp-FileBrowser-Panel';
23
+
24
+ /**
25
+ * The class name added to the filebrowser crumbs node.
26
+ */
27
+ const CRUMBS_CLASS = 'jp-FileBrowser-crumbs';
28
+
29
+ /**
30
+ * The class name added to the filebrowser toolbar node.
31
+ */
32
+ const TOOLBAR_CLASS = 'jp-FileBrowser-toolbar';
33
+
34
+ /**
35
+ * The class name added to the filebrowser listing node.
36
+ */
37
+ const LISTING_CLASS = 'jp-FileBrowser-listing';
38
+
39
+ /**
40
+ * A widget which hosts a file browser.
41
+ *
42
+ * The widget uses the Jupyter Contents API to retrieve contents,
43
+ * and presents itself as a flat list of files and directories with
44
+ * breadcrumbs.
45
+ */
46
+ export class FileBrowser extends SidePanel {
47
+ /**
48
+ * Construct a new file browser.
49
+ *
50
+ * @param options - The file browser options.
51
+ */
52
+ constructor(options: FileBrowser.IOptions) {
53
+ super({ content: new Panel(), translator: options.translator });
54
+ this.addClass(FILE_BROWSER_CLASS);
55
+ this.toolbar.addClass(TOOLBAR_CLASS);
56
+ this.id = options.id;
57
+ const translator = (this.translator = options.translator ?? nullTranslator);
58
+
59
+ const model = (this.model = options.model);
60
+ const renderer = options.renderer;
61
+
62
+ model.connectionFailure.connect(this._onConnectionFailure, this);
63
+ this._manager = model.manager;
64
+
65
+ // a11y
66
+ this.toolbar.node.setAttribute('role', 'navigation');
67
+ this.toolbar.node.setAttribute(
68
+ 'aria-label',
69
+ this._trans.__('file browser')
70
+ );
71
+
72
+ this._directoryPending = false;
73
+
74
+ // File browser widgets container
75
+ this.mainPanel = new Panel();
76
+ this.mainPanel.addClass(FILE_BROWSER_PANEL_CLASS);
77
+ this.mainPanel.title.label = this._trans.__('File Browser');
78
+
79
+ this.crumbs = new BreadCrumbs({ model, translator });
80
+ this.crumbs.addClass(CRUMBS_CLASS);
81
+
82
+ this.listing = this.createDirListing({
83
+ model,
84
+ renderer,
85
+ translator
86
+ });
87
+ this.listing.addClass(LISTING_CLASS);
88
+
89
+ this.mainPanel.addWidget(this.crumbs);
90
+ this.mainPanel.addWidget(this.listing);
91
+
92
+ this.addWidget(this.mainPanel);
93
+
94
+ if (options.restore !== false) {
95
+ void model.restore(this.id);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * The model used by the file browser.
101
+ */
102
+ readonly model: FilterFileBrowserModel;
103
+
104
+ /**
105
+ * Whether to show active file in file browser
106
+ */
107
+ get navigateToCurrentDirectory(): boolean {
108
+ return this._navigateToCurrentDirectory;
109
+ }
110
+
111
+ set navigateToCurrentDirectory(value: boolean) {
112
+ this._navigateToCurrentDirectory = value;
113
+ }
114
+
115
+ /**
116
+ * Whether to show the last modified column
117
+ */
118
+ get showLastModifiedColumn(): boolean {
119
+ return this._showLastModifiedColumn;
120
+ }
121
+
122
+ set showLastModifiedColumn(value: boolean) {
123
+ if (this.listing.setColumnVisibility) {
124
+ this.listing.setColumnVisibility('last_modified', value);
125
+ this._showLastModifiedColumn = value;
126
+ } else {
127
+ console.warn('Listing does not support toggling column visibility');
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Whether to show the file size column
133
+ */
134
+ get showFileSizeColumn(): boolean {
135
+ return this._showFileSizeColumn;
136
+ }
137
+
138
+ set showFileSizeColumn(value: boolean) {
139
+ if (this.listing.setColumnVisibility) {
140
+ this.listing.setColumnVisibility('file_size', value);
141
+ this._showFileSizeColumn = value;
142
+ } else {
143
+ console.warn('Listing does not support toggling column visibility');
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Whether to show hidden files
149
+ */
150
+ get showHiddenFiles(): boolean {
151
+ return this._showHiddenFiles;
152
+ }
153
+
154
+ set showHiddenFiles(value: boolean) {
155
+ this.model.showHiddenFiles(value);
156
+ this._showHiddenFiles = value;
157
+ }
158
+
159
+ /**
160
+ * Whether to show checkboxes next to files and folders
161
+ */
162
+ get showFileCheckboxes(): boolean {
163
+ return this._showFileCheckboxes;
164
+ }
165
+
166
+ set showFileCheckboxes(value: boolean) {
167
+ if (this.listing.setColumnVisibility) {
168
+ this.listing.setColumnVisibility('is_selected', value);
169
+ this._showFileCheckboxes = value;
170
+ } else {
171
+ console.warn('Listing does not support toggling column visibility');
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Create an iterator over the listing's selected items.
177
+ *
178
+ * @returns A new iterator over the listing's selected items.
179
+ */
180
+ selectedItems(): IterableIterator<Contents.IModel> {
181
+ return this.listing.selectedItems();
182
+ }
183
+
184
+ /**
185
+ * Select an item by name.
186
+ *
187
+ * @param name - The name of the item to select.
188
+ */
189
+ async selectItemByName(name: string): Promise<void> {
190
+ await this.listing.selectItemByName(name);
191
+ }
192
+
193
+ clearSelectedItems(): void {
194
+ this.listing.clearSelectedItems();
195
+ }
196
+
197
+ /**
198
+ * Rename the first currently selected item.
199
+ *
200
+ * @returns A promise that resolves with the new name of the item.
201
+ */
202
+ rename(): Promise<string> {
203
+ return this.listing.rename();
204
+ }
205
+
206
+ /**
207
+ * Cut the selected items.
208
+ */
209
+ cut(): void {
210
+ this.listing.cut();
211
+ }
212
+
213
+ /**
214
+ * Copy the selected items.
215
+ */
216
+ copy(): void {
217
+ this.listing.copy();
218
+ }
219
+
220
+ /**
221
+ * Paste the items from the clipboard.
222
+ *
223
+ * @returns A promise that resolves when the operation is complete.
224
+ */
225
+ paste(): Promise<void> {
226
+ return this.listing.paste();
227
+ }
228
+
229
+ /**
230
+ * Create a new directory
231
+ */
232
+ createNewDirectory(): void {
233
+ if (this._directoryPending === true) {
234
+ return;
235
+ }
236
+ this._directoryPending = true;
237
+ // TODO: We should provide a hook into when the
238
+ // directory is done being created. This probably
239
+ // means storing a pendingDirectory promise and
240
+ // returning that if there is already a directory
241
+ // request.
242
+ void this._manager
243
+ .newUntitled({
244
+ path: this.model.path,
245
+ type: 'directory'
246
+ })
247
+ .then(async model => {
248
+ await this.listing.selectItemByName(model.name);
249
+ await this.rename();
250
+ this._directoryPending = false;
251
+ })
252
+ .catch(err => {
253
+ void showErrorMessage(this._trans.__('Error'), err);
254
+ this._directoryPending = false;
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Create a new file
260
+ */
261
+ createNewFile(options: FileBrowser.IFileOptions): void {
262
+ if (this._filePending === true) {
263
+ return;
264
+ }
265
+ this._filePending = true;
266
+ // TODO: We should provide a hook into when the
267
+ // file is done being created. This probably
268
+ // means storing a pendingFile promise and
269
+ // returning that if there is already a file
270
+ // request.
271
+ void this._manager
272
+ .newUntitled({
273
+ path: this.model.path,
274
+ type: 'file',
275
+ ext: options.ext
276
+ })
277
+ .then(async model => {
278
+ await this.listing.selectItemByName(model.name);
279
+ await this.rename();
280
+ this._filePending = false;
281
+ })
282
+ .catch(err => {
283
+ void showErrorMessage(this._trans.__('Error'), err);
284
+ this._filePending = false;
285
+ });
286
+ }
287
+
288
+ /**
289
+ * Delete the currently selected item(s).
290
+ *
291
+ * @returns A promise that resolves when the operation is complete.
292
+ */
293
+ delete(): Promise<void> {
294
+ return this.listing.delete();
295
+ }
296
+
297
+ /**
298
+ * Duplicate the currently selected item(s).
299
+ *
300
+ * @returns A promise that resolves when the operation is complete.
301
+ */
302
+ duplicate(): Promise<void> {
303
+ return this.listing.duplicate();
304
+ }
305
+
306
+ /**
307
+ * Download the currently selected item(s).
308
+ */
309
+ download(): Promise<void> {
310
+ return this.listing.download();
311
+ }
312
+
313
+ /**
314
+ * Shut down kernels on the applicable currently selected items.
315
+ *
316
+ * @returns A promise that resolves when the operation is complete.
317
+ */
318
+ shutdownKernels(): Promise<void> {
319
+ return this.listing.shutdownKernels();
320
+ }
321
+
322
+ /**
323
+ * Select next item.
324
+ */
325
+ selectNext(): void {
326
+ this.listing.selectNext();
327
+ }
328
+
329
+ /**
330
+ * Select previous item.
331
+ */
332
+ selectPrevious(): void {
333
+ this.listing.selectPrevious();
334
+ }
335
+
336
+ /**
337
+ * Find a model given a click.
338
+ *
339
+ * @param event - The mouse event.
340
+ *
341
+ * @returns The model for the selected file.
342
+ */
343
+ modelForClick(event: MouseEvent): Contents.IModel | undefined {
344
+ return this.listing.modelForClick(event);
345
+ }
346
+
347
+ /**
348
+ * Create the underlying DirListing instance.
349
+ *
350
+ * @param options - The DirListing constructor options.
351
+ *
352
+ * @returns The created DirListing instance.
353
+ */
354
+ protected createDirListing(options: DirListing.IOptions): DirListing {
355
+ return new DirListing(options);
356
+ }
357
+
358
+ protected translator: ITranslator;
359
+
360
+ /**
361
+ * Handle a connection lost signal from the model.
362
+ */
363
+ private _onConnectionFailure(
364
+ sender: FilterFileBrowserModel,
365
+ args: Error
366
+ ): void {
367
+ if (
368
+ args instanceof ServerConnection.ResponseError &&
369
+ args.response.status === 404
370
+ ) {
371
+ const title = this._trans.__('Directory not found');
372
+ args.message = this._trans.__(
373
+ 'Directory not found: "%1"',
374
+ this.model.path
375
+ );
376
+ void showErrorMessage(title, args);
377
+ }
378
+ }
379
+
380
+ protected listing: DirListing;
381
+ protected crumbs: BreadCrumbs;
382
+ protected mainPanel: Panel;
383
+
384
+ private _manager: IDocumentManager;
385
+ private _directoryPending: boolean;
386
+ private _filePending: boolean;
387
+ private _navigateToCurrentDirectory: boolean;
388
+ private _showLastModifiedColumn: boolean = true;
389
+ private _showFileSizeColumn: boolean = false;
390
+ private _showHiddenFiles: boolean = false;
391
+ private _showFileCheckboxes: boolean = false;
392
+ }
393
+
394
+ /**
395
+ * The namespace for the `FileBrowser` class statics.
396
+ */
397
+ export namespace FileBrowser {
398
+ /**
399
+ * An options object for initializing a file browser widget.
400
+ */
401
+ export interface IOptions {
402
+ /**
403
+ * The widget/DOM id of the file browser.
404
+ */
405
+ id: string;
406
+
407
+ /**
408
+ * A file browser model instance.
409
+ */
410
+ model: FilterFileBrowserModel;
411
+
412
+ /**
413
+ * An optional renderer for the directory listing area.
414
+ *
415
+ * The default is a shared instance of `DirListing.Renderer`.
416
+ */
417
+ renderer?: DirListing.IRenderer;
418
+
419
+ /**
420
+ * Whether a file browser automatically restores state when instantiated.
421
+ * The default is `true`.
422
+ *
423
+ * #### Notes
424
+ * The file browser model will need to be restored manually for the file
425
+ * browser to be able to save its state.
426
+ */
427
+ restore?: boolean;
428
+
429
+ /**
430
+ * The application language translator.
431
+ */
432
+ translator?: ITranslator;
433
+ }
434
+
435
+ /**
436
+ * An options object for creating a file.
437
+ */
438
+ export interface IFileOptions {
439
+ /**
440
+ * The file extension.
441
+ */
442
+ ext: string;
443
+ }
444
+ }