@jupyterlab/filebrowser 4.3.0-alpha.2 → 4.3.0-beta.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.
package/lib/listing.js CHANGED
@@ -9,6 +9,7 @@ import { caretDownIcon, caretUpIcon, classes, LabIcon } from '@jupyterlab/ui-com
9
9
  import { ArrayExt, filter, StringExt } from '@lumino/algorithm';
10
10
  import { MimeData, PromiseDelegate } from '@lumino/coreutils';
11
11
  import { ElementExt } from '@lumino/domutils';
12
+ import { DisposableDelegate } from '@lumino/disposable';
12
13
  import { Drag } from '@lumino/dragdrop';
13
14
  import { MessageLoop } from '@lumino/messaging';
14
15
  import { Signal } from '@lumino/signaling';
@@ -46,6 +47,10 @@ const ITEM_CLASS = 'jp-DirListing-item';
46
47
  * The class name added to the listing item text cell.
47
48
  */
48
49
  const ITEM_TEXT_CLASS = 'jp-DirListing-itemText';
50
+ /**
51
+ * The class name added to the listing item text cell.
52
+ */
53
+ const ITEM_NAME_COLUMN_CLASS = 'jp-DirListing-itemName';
49
54
  /**
50
55
  * The class name added to the listing item icon cell.
51
56
  */
@@ -79,18 +84,6 @@ const MODIFIED_ID_CLASS = 'jp-id-modified';
79
84
  * The class name added to the file size column header cell.
80
85
  */
81
86
  const FILE_SIZE_ID_CLASS = 'jp-id-filesize';
82
- /**
83
- * The class name added to the narrow column header cell.
84
- */
85
- const NARROW_ID_CLASS = 'jp-id-narrow';
86
- /**
87
- * The class name added to the modified column header cell and modified item cell when hidden.
88
- */
89
- const MODIFIED_COLUMN_HIDDEN = 'jp-LastModified-hidden';
90
- /**
91
- * The class name added to the size column header cell and size item cell when hidden.
92
- */
93
- const FILE_SIZE_COLUMN_HIDDEN = 'jp-FileSize-hidden';
94
87
  /**
95
88
  * The mime type for a contents drag object.
96
89
  */
@@ -111,6 +104,10 @@ const SELECTED_CLASS = 'jp-mod-selected';
111
104
  * The class name added to drag state icons to add space between the icon and the file name
112
105
  */
113
106
  const DRAG_ICON_CLASS = 'jp-DragIcon';
107
+ /**
108
+ * The class name added to column resize handle.
109
+ */
110
+ const RESIZE_HANDLE_CLASS = 'jp-DirListing-resizeHandle';
114
111
  /**
115
112
  * The class name added to the widget when there are items on the clipboard.
116
113
  */
@@ -127,6 +124,10 @@ const MULTI_SELECTED_CLASS = 'jp-mod-multiSelected';
127
124
  * The class name added to indicate running notebook.
128
125
  */
129
126
  const RUNNING_CLASS = 'jp-mod-running';
127
+ /**
128
+ * The class name added to indicate the active element.
129
+ */
130
+ const ACTIVE_CLASS = 'jp-mod-active';
130
131
  /**
131
132
  * The class name added for a descending sort.
132
133
  */
@@ -169,6 +170,7 @@ export class DirListing extends Widget {
169
170
  this._onItemOpened = new Signal(this);
170
171
  this._drag = null;
171
172
  this._dragData = null;
173
+ this._resizeData = null;
172
174
  this._selectTimer = -1;
173
175
  this._isCut = false;
174
176
  this._prevPath = '';
@@ -180,9 +182,19 @@ export class DirListing extends Widget {
180
182
  this._inRename = false;
181
183
  this._isDirty = false;
182
184
  this._hiddenColumns = new Set();
185
+ this._columnSizes = {
186
+ name: null,
187
+ file_size: null,
188
+ is_selected: null,
189
+ last_modified: null
190
+ };
183
191
  this._sortNotebooksFirst = false;
192
+ this._allowSingleClick = false;
184
193
  // _focusIndex should never be set outside the range [0, this._items.length - 1]
185
194
  this._focusIndex = 0;
195
+ this._allUploaded = new Signal(this);
196
+ this._width = null;
197
+ this._state = null;
186
198
  this.addClass(DIR_LISTING_CLASS);
187
199
  this.translator = options.translator || nullTranslator;
188
200
  this._trans = this.translator.load('jupyterlab');
@@ -194,12 +206,13 @@ export class DirListing extends Widget {
194
206
  this._editNode.className = EDITOR_CLASS;
195
207
  this._manager = this._model.manager;
196
208
  this._renderer = options.renderer || DirListing.defaultRenderer;
209
+ this._state = options.state || null;
197
210
  // Get the width of the "modified" column
198
211
  this._updateModifiedSize(this.node);
199
212
  const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
200
213
  // hide the file size column by default
201
214
  this._hiddenColumns.add('file_size');
202
- this._renderer.populateHeaderNode(headerNode, this.translator, this._hiddenColumns);
215
+ this._renderer.populateHeaderNode(headerNode, this.translator, this._hiddenColumns, this._columnSizes);
203
216
  this._manager.activateRequested.connect(this._onActivateRequested, this);
204
217
  }
205
218
  /**
@@ -409,6 +422,37 @@ export class DirListing extends Widget {
409
422
  .filter(item => item.type !== 'directory')
410
423
  .map(item => this._model.download(item.path)));
411
424
  }
425
+ /**
426
+ * Restore the state of the file browser listing.
427
+ *
428
+ * @param id - The unique ID that is used to construct a state database key.
429
+ *
430
+ */
431
+ async restore(id) {
432
+ const key = `file-browser-${id}:columns`;
433
+ const state = this._state;
434
+ this._stateColumnsKey = key;
435
+ if (!state) {
436
+ return;
437
+ }
438
+ try {
439
+ const columns = await state.fetch(key);
440
+ if (!columns) {
441
+ return;
442
+ }
443
+ const sizes = columns['sizes'];
444
+ if (!sizes) {
445
+ return;
446
+ }
447
+ for (const [key, size] of Object.entries(sizes)) {
448
+ this._columnSizes[key] = size;
449
+ }
450
+ this._updateColumnSizes();
451
+ }
452
+ catch (error) {
453
+ await state.remove(key);
454
+ }
455
+ }
412
456
  /**
413
457
  * Shut down kernels on the applicable currently selected items.
414
458
  *
@@ -649,6 +693,7 @@ export class DirListing extends Widget {
649
693
  onAfterAttach(msg) {
650
694
  super.onAfterAttach(msg);
651
695
  const node = this.node;
696
+ this._width = this.node.getBoundingClientRect().width;
652
697
  const content = DOMUtils.findElement(node, CONTENT_CLASS);
653
698
  node.addEventListener('mousedown', this);
654
699
  node.addEventListener('keydown', this);
@@ -699,12 +744,15 @@ export class DirListing extends Widget {
699
744
  this.update();
700
745
  }
701
746
  }
702
- // Update the modified column's size
747
+ /**
748
+ * Update the modified column's size
749
+ */
703
750
  _updateModifiedSize(node) {
704
- var _a;
751
+ var _a, _b;
705
752
  // Look for the modified column's header
706
753
  const modified = DOMUtils.findElement(node, MODIFIED_ID_CLASS);
707
- this._modifiedWidth = (_a = modified === null || modified === void 0 ? void 0 : modified.getBoundingClientRect().width) !== null && _a !== void 0 ? _a : 83;
754
+ this._modifiedWidth =
755
+ (_b = (_a = this._columnSizes['last_modified']) !== null && _a !== void 0 ? _a : modified === null || modified === void 0 ? void 0 : modified.getBoundingClientRect().width) !== null && _b !== void 0 ? _b : 83;
708
756
  this._modifiedStyle =
709
757
  this._modifiedWidth < 100
710
758
  ? 'narrow'
@@ -712,7 +760,20 @@ export class DirListing extends Widget {
712
760
  ? 'long'
713
761
  : 'short';
714
762
  }
715
- // Update only the modified dates.
763
+ /**
764
+ * Rerender item nodes' modified dates, if the modified style has changed.
765
+ */
766
+ _updateModifiedStyleAndSize() {
767
+ const oldModifiedStyle = this._modifiedStyle;
768
+ // Update both size and style
769
+ this._updateModifiedSize(this.node);
770
+ if (oldModifiedStyle !== this._modifiedStyle) {
771
+ this.updateModified(this._sortedItems, this._items);
772
+ }
773
+ }
774
+ /**
775
+ * Update only the modified dates.
776
+ */
716
777
  updateModified(items, nodes) {
717
778
  items.forEach((item, i) => {
718
779
  const node = nodes[i];
@@ -728,12 +789,19 @@ export class DirListing extends Widget {
728
789
  });
729
790
  }
730
791
  // Update item nodes based on widget state.
731
- updateNodes(items, nodes) {
792
+ updateNodes(items, nodes, sizeOnly = false) {
732
793
  var _a;
733
794
  items.forEach((item, i) => {
734
795
  const node = nodes[i];
796
+ if (sizeOnly && this.renderer.updateItemSize) {
797
+ if (!node) {
798
+ // short-circuit in case if node is not yet ready
799
+ return;
800
+ }
801
+ return this.renderer.updateItemSize(node, item, this._modifiedStyle, this._columnSizes);
802
+ }
735
803
  const ft = this._manager.registry.getFileTypeForModel(item);
736
- this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle);
804
+ this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle, this._columnSizes);
737
805
  if (this.selection[item.path] &&
738
806
  this._isCut &&
739
807
  this._model.path === this._prevPath) {
@@ -786,7 +854,7 @@ export class DirListing extends Widget {
786
854
  }
787
855
  // Add any missing item nodes.
788
856
  while (nodes.length < items.length) {
789
- const node = renderer.createItemNode(this._hiddenColumns);
857
+ const node = renderer.createItemNode(this._hiddenColumns, this._columnSizes);
790
858
  node.classList.add(ITEM_CLASS);
791
859
  nodes.push(node);
792
860
  content.appendChild(node);
@@ -833,14 +901,8 @@ export class DirListing extends Widget {
833
901
  }
834
902
  onResize(msg) {
835
903
  const { width } = msg.width === -1 ? this.node.getBoundingClientRect() : msg;
836
- this.toggleClass('jp-DirListing-narrow', width < 250);
837
- // Rerender item nodes' modified dates, if the modified style has changed.
838
- const oldModifiedStyle = this._modifiedStyle;
839
- // Update both size and style
840
- this._updateModifiedSize(this.node);
841
- if (oldModifiedStyle !== this._modifiedStyle) {
842
- this.updateModified(this._sortedItems, this._items);
843
- }
904
+ this._width = width;
905
+ this._updateColumnSizes();
844
906
  }
845
907
  setColumnVisibility(name, visible) {
846
908
  if (visible) {
@@ -850,7 +912,99 @@ export class DirListing extends Widget {
850
912
  this._hiddenColumns.add(name);
851
913
  }
852
914
  this.headerNode.innerHTML = '';
853
- this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns);
915
+ this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns, this._columnSizes);
916
+ this._updateColumnSizes();
917
+ }
918
+ _updateColumnSizes() {
919
+ // adjust column sizes so that they add up to the total width available, preserving ratios
920
+ // and removing width from the last column so that it fills the width available;
921
+ const visibleColumns = this._visibleColumns.map(column => ({
922
+ ...column,
923
+ element: DOMUtils.findElement(this.node, column.className)
924
+ }));
925
+ // read from DOM
926
+ let total = 0;
927
+ for (const column of visibleColumns) {
928
+ let size = this._columnSizes[column.id];
929
+ if (size === null) {
930
+ size = column.element.getBoundingClientRect().width;
931
+ }
932
+ // restrict the minimum and maximum width
933
+ size = Math.max(size, column.minWidth);
934
+ if (this._width) {
935
+ let reservedForOtherColums = 0;
936
+ for (const other of visibleColumns) {
937
+ if (other.id === column.id) {
938
+ continue;
939
+ }
940
+ reservedForOtherColums += other.minWidth;
941
+ }
942
+ size = Math.min(size, this._width - reservedForOtherColums);
943
+ }
944
+ this._columnSizes[column.id] = size;
945
+ total += size;
946
+ }
947
+ // Ensure that total fits
948
+ if (this._width && total > this._width) {
949
+ for (const column of visibleColumns) {
950
+ this._columnSizes[column.id] =
951
+ (this._columnSizes[column.id] / total) * this._width;
952
+ }
953
+ }
954
+ // Write to DOM
955
+ for (const column of visibleColumns) {
956
+ const size = this._columnSizes[column.id];
957
+ column.element.style.width = size === null ? '' : size + 'px';
958
+ }
959
+ this._updateModifiedStyleAndSize();
960
+ // Refresh sizes on the per item widths
961
+ if (this.isVisible) {
962
+ const items = this._items;
963
+ if (items.length !== 0) {
964
+ this.updateNodes(this._sortedItems, this._items, true);
965
+ }
966
+ }
967
+ if (this._state && this._stateColumnsKey) {
968
+ void this._state.save(this._stateColumnsKey, {
969
+ sizes: this._columnSizes
970
+ });
971
+ }
972
+ }
973
+ get _visibleColumns() {
974
+ return DirListing.columns.filter(column => { var _a; return column.id === 'name' || !((_a = this._hiddenColumns) === null || _a === void 0 ? void 0 : _a.has(column.id)); });
975
+ }
976
+ _setColumnSize(name, size) {
977
+ var _a;
978
+ const previousSize = this._columnSizes[name];
979
+ if (previousSize && size && size > previousSize) {
980
+ // check if we can resize up
981
+ let total = 0;
982
+ let before = true;
983
+ for (const column of this._visibleColumns) {
984
+ if (column.id === name) {
985
+ // add proposed size for the current columns
986
+ total += size;
987
+ before = false;
988
+ continue;
989
+ }
990
+ if (before) {
991
+ // add size as-is for columns before
992
+ const element = DOMUtils.findElement(this.node, column.className);
993
+ total +=
994
+ (_a = this._columnSizes[column.id]) !== null && _a !== void 0 ? _a : element.getBoundingClientRect().width;
995
+ }
996
+ else {
997
+ // add minimum acceptable size for columns after
998
+ total += column.minWidth;
999
+ }
1000
+ }
1001
+ if (this._width && total > this._width) {
1002
+ // up sizing is no longer possible
1003
+ return;
1004
+ }
1005
+ }
1006
+ this._columnSizes[name] = size;
1007
+ this._updateColumnSizes();
854
1008
  }
855
1009
  /**
856
1010
  * Update the setting to sort notebooks above files.
@@ -863,6 +1017,13 @@ export class DirListing extends Widget {
863
1017
  this.sort(this._sortState);
864
1018
  }
865
1019
  }
1020
+ /**
1021
+ * Update the setting to allow single click navigation.
1022
+ * This enables opening files/directories with a single click.
1023
+ */
1024
+ setAllowSingleClickNavigation(isEnabled) {
1025
+ this._allowSingleClick = isEnabled;
1026
+ }
866
1027
  /**
867
1028
  * Would this click (or other event type) hit the checkbox by default?
868
1029
  */
@@ -945,6 +1106,36 @@ export class DirListing extends Widget {
945
1106
  }
946
1107
  let index = Private.hitTestNodes(this._items, event);
947
1108
  if (index === -1) {
1109
+ // Left mouse press for drag or resize start.
1110
+ if (event.button === 0) {
1111
+ const resizeHandle = event.target;
1112
+ if (resizeHandle instanceof HTMLElement &&
1113
+ resizeHandle.classList.contains(RESIZE_HANDLE_CLASS)) {
1114
+ const columnId = resizeHandle.dataset.column;
1115
+ if (!columnId) {
1116
+ throw Error('Column resize handle is missing data-column attribute');
1117
+ }
1118
+ const column = DirListing.columns.find(c => c.id === columnId);
1119
+ if (!column) {
1120
+ throw Error(`Column with identifier ${columnId} not found`);
1121
+ }
1122
+ const element = DOMUtils.findElement(this.node, column.className);
1123
+ resizeHandle.classList.add(ACTIVE_CLASS);
1124
+ const cursorOverride = Drag.overrideCursor('col-resize');
1125
+ this._resizeData = {
1126
+ pressX: event.clientX,
1127
+ column: columnId,
1128
+ initialSize: element.getBoundingClientRect().width,
1129
+ overrides: new DisposableDelegate(() => {
1130
+ cursorOverride.dispose();
1131
+ resizeHandle.classList.remove(ACTIVE_CLASS);
1132
+ })
1133
+ };
1134
+ document.addEventListener('mouseup', this, true);
1135
+ document.addEventListener('mousemove', this, true);
1136
+ return;
1137
+ }
1138
+ }
948
1139
  return;
949
1140
  }
950
1141
  this.handleFileSelect(event);
@@ -956,7 +1147,7 @@ export class DirListing extends Widget {
956
1147
  if (newContext) {
957
1148
  return;
958
1149
  }
959
- // Left mouse press for drag start.
1150
+ // Left mouse press for drag or resize start.
960
1151
  if (event.button === 0) {
961
1152
  this._dragData = {
962
1153
  pressX: event.clientX,
@@ -966,6 +1157,9 @@ export class DirListing extends Widget {
966
1157
  document.addEventListener('mouseup', this, true);
967
1158
  document.addEventListener('mousemove', this, true);
968
1159
  }
1160
+ if (this._allowSingleClick) {
1161
+ this.evtDblClick(event);
1162
+ }
969
1163
  }
970
1164
  /**
971
1165
  * Handle the `'mouseup'` event for the widget.
@@ -988,6 +1182,13 @@ export class DirListing extends Widget {
988
1182
  if (event.button === 0) {
989
1183
  this._focusItem(this._focusIndex);
990
1184
  }
1185
+ // Remove the resize listeners if necessary.
1186
+ if (this._resizeData) {
1187
+ this._resizeData.overrides.dispose();
1188
+ document.removeEventListener('mousemove', this, true);
1189
+ document.removeEventListener('mouseup', this, true);
1190
+ return;
1191
+ }
991
1192
  // Remove the drag listeners if necessary.
992
1193
  if (event.button !== 0 || !this._drag) {
993
1194
  document.removeEventListener('mousemove', this, true);
@@ -1003,6 +1204,11 @@ export class DirListing extends Widget {
1003
1204
  _evtMousemove(event) {
1004
1205
  event.preventDefault();
1005
1206
  event.stopPropagation();
1207
+ if (this._resizeData) {
1208
+ const { initialSize, column, pressX } = this._resizeData;
1209
+ this._setColumnSize(column, initialSize + event.clientX - pressX);
1210
+ return;
1211
+ }
1006
1212
  // Bail if we are the one dragging.
1007
1213
  if (this._drag || !this._dragData) {
1008
1214
  return;
@@ -1245,29 +1451,61 @@ export class DirListing extends Widget {
1245
1451
  */
1246
1452
  evtNativeDrop(event) {
1247
1453
  var _a, _b, _c;
1248
- const files = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.files;
1249
- if (!files || files.length === 0) {
1250
- return;
1251
- }
1252
- const length = (_b = event.dataTransfer) === null || _b === void 0 ? void 0 : _b.items.length;
1253
- if (!length) {
1454
+ // Prevent navigation
1455
+ event.preventDefault();
1456
+ const items = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.items;
1457
+ if (!items) {
1458
+ // Fallback to simple upload of files (if any)
1459
+ const files = (_b = event.dataTransfer) === null || _b === void 0 ? void 0 : _b.files;
1460
+ if (!files || files.length === 0) {
1461
+ return;
1462
+ }
1463
+ const promises = [];
1464
+ for (const file of files) {
1465
+ const promise = this._model.upload(file);
1466
+ promises.push(promise);
1467
+ }
1468
+ Promise.all(promises)
1469
+ .then(() => this._allUploaded.emit())
1470
+ .catch(err => {
1471
+ console.error('Error while uploading files: ', err);
1472
+ });
1254
1473
  return;
1255
1474
  }
1256
- for (let i = 0; i < length; i++) {
1257
- let entry = (_c = event.dataTransfer) === null || _c === void 0 ? void 0 : _c.items[i].webkitGetAsEntry();
1258
- if (entry === null || entry === void 0 ? void 0 : entry.isDirectory) {
1259
- console.log('currently not supporting drag + drop for folders');
1260
- void showDialog({
1261
- title: this._trans.__('Error Uploading Folder'),
1262
- body: this._trans.__('Drag and Drop is currently not supported for folders'),
1263
- buttons: [Dialog.cancelButton({ label: this._trans.__('Close') })]
1264
- });
1475
+ const uploadEntry = async (entry, path) => {
1476
+ if (Private.isDirectoryEntry(entry)) {
1477
+ const dirPath = await Private.createDirectory(this._model.manager, path, entry.name);
1478
+ const directoryReader = entry.createReader();
1479
+ const allEntries = await Private.collectEntries(directoryReader);
1480
+ for (const childEntry of allEntries) {
1481
+ await uploadEntry(childEntry, dirPath);
1482
+ }
1265
1483
  }
1484
+ else if (Private.isFileEntry(entry)) {
1485
+ const file = await Private.readFile(entry);
1486
+ await this._model.upload(file, path);
1487
+ }
1488
+ };
1489
+ const promises = [];
1490
+ for (const item of items) {
1491
+ const entry = Private.defensiveGetAsEntry(item);
1492
+ if (!entry) {
1493
+ continue;
1494
+ }
1495
+ const promise = uploadEntry(entry, (_c = this._model.path) !== null && _c !== void 0 ? _c : '/');
1496
+ promises.push(promise);
1266
1497
  }
1267
- event.preventDefault();
1268
- for (let i = 0; i < files.length; i++) {
1269
- void this._model.upload(files[i]);
1270
- }
1498
+ Promise.all(promises)
1499
+ .then(() => this._allUploaded.emit())
1500
+ .catch(err => {
1501
+ console.error('Error while uploading files: ', err);
1502
+ });
1503
+ }
1504
+ /**
1505
+ * Signal emitted on when all files were uploaded after native drag.
1506
+ */
1507
+ get allUploaded() {
1508
+ return this._allUploaded;
1271
1509
  }
1272
1510
  /**
1273
1511
  * Handle the `'lm-dragenter'` event for the widget.
@@ -1801,10 +2039,83 @@ export class DirListing extends Widget {
1801
2039
  * The namespace for the `DirListing` class statics.
1802
2040
  */
1803
2041
  (function (DirListing) {
2042
+ /**
2043
+ * Column definitions.
2044
+ */
2045
+ DirListing.columns = [
2046
+ {
2047
+ id: 'is_selected',
2048
+ className: CHECKBOX_WRAPPER_CLASS,
2049
+ itemClassName: CHECKBOX_WRAPPER_CLASS,
2050
+ minWidth: 18,
2051
+ resizable: false,
2052
+ sortable: false
2053
+ },
2054
+ {
2055
+ id: 'name',
2056
+ className: NAME_ID_CLASS,
2057
+ itemClassName: ITEM_NAME_COLUMN_CLASS,
2058
+ minWidth: 60,
2059
+ resizable: true,
2060
+ sortable: true,
2061
+ caretSide: 'right'
2062
+ },
2063
+ {
2064
+ id: 'last_modified',
2065
+ className: MODIFIED_ID_CLASS,
2066
+ itemClassName: ITEM_MODIFIED_CLASS,
2067
+ minWidth: 60,
2068
+ resizable: true,
2069
+ sortable: true,
2070
+ caretSide: 'left'
2071
+ },
2072
+ {
2073
+ id: 'file_size',
2074
+ className: FILE_SIZE_ID_CLASS,
2075
+ itemClassName: ITEM_FILE_SIZE_CLASS,
2076
+ minWidth: 60,
2077
+ resizable: true,
2078
+ sortable: true,
2079
+ caretSide: 'left'
2080
+ }
2081
+ ];
1804
2082
  /**
1805
2083
  * The default implementation of an `IRenderer`.
1806
2084
  */
1807
2085
  class Renderer {
2086
+ constructor() {
2087
+ /**
2088
+ * Factories for individual parts of the item.
2089
+ */
2090
+ this.itemFactories = {
2091
+ name: () => {
2092
+ const name = document.createElement('span');
2093
+ const icon = document.createElement('span');
2094
+ const text = document.createElement('span');
2095
+ icon.className = ITEM_ICON_CLASS;
2096
+ text.className = ITEM_TEXT_CLASS;
2097
+ name.className = ITEM_NAME_COLUMN_CLASS;
2098
+ name.appendChild(icon);
2099
+ name.appendChild(text);
2100
+ return name;
2101
+ },
2102
+ last_modified: () => {
2103
+ const modified = document.createElement('span');
2104
+ modified.className = ITEM_MODIFIED_CLASS;
2105
+ return modified;
2106
+ },
2107
+ file_size: () => {
2108
+ const fileSize = document.createElement('span');
2109
+ fileSize.className = ITEM_FILE_SIZE_CLASS;
2110
+ return fileSize;
2111
+ },
2112
+ is_selected: () => this.createCheckboxWrapperNode()
2113
+ };
2114
+ /**
2115
+ * Register of most recent arguments for last modified column update.
2116
+ */
2117
+ this._modifiedColumnLastUpdate = new WeakMap();
2118
+ }
1808
2119
  /**
1809
2120
  * Create the DOM node for a dir listing.
1810
2121
  */
@@ -1827,45 +2138,43 @@ export class DirListing extends Widget {
1827
2138
  *
1828
2139
  * @param node - The header node to populate.
1829
2140
  */
1830
- populateHeaderNode(node, translator, hiddenColumns) {
2141
+ populateHeaderNode(node, translator, hiddenColumns, columnsSizes) {
1831
2142
  translator = translator || nullTranslator;
1832
2143
  const trans = translator.load('jupyterlab');
1833
- const name = this.createHeaderItemNode(trans.__('Name'));
1834
- const narrow = document.createElement('div');
1835
- const modified = this._createHeaderItemNodeWithSizes({
1836
- small: trans.__('Modified'),
1837
- large: trans.__('Last Modified')
1838
- });
1839
- const fileSize = this.createHeaderItemNode(trans.__('File Size'));
1840
- name.classList.add(NAME_ID_CLASS);
1841
- name.classList.add(SELECTED_CLASS);
1842
- modified.classList.add(MODIFIED_ID_CLASS);
1843
- fileSize.classList.add(FILE_SIZE_ID_CLASS);
1844
- narrow.classList.add(NARROW_ID_CLASS);
1845
- narrow.textContent = '...';
1846
- if (!(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'))) {
1847
- const checkboxWrapper = this.createCheckboxWrapperNode({
2144
+ const elementCreators = {
2145
+ name: () => this.createHeaderItemNode(trans.__('Name')),
2146
+ last_modified: () => this._createHeaderItemNodeWithSizes({
2147
+ small: trans.__('Modified'),
2148
+ large: trans.__('Last Modified')
2149
+ }),
2150
+ file_size: () => this.createHeaderItemNode(trans.__('File Size')),
2151
+ is_selected: () => this.createCheckboxWrapperNode({
1848
2152
  alwaysVisible: true,
1849
2153
  headerNode: true
1850
- });
1851
- node.appendChild(checkboxWrapper);
1852
- }
1853
- node.appendChild(name);
1854
- node.appendChild(narrow);
1855
- node.appendChild(modified);
1856
- node.appendChild(fileSize);
1857
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
1858
- modified.classList.add(MODIFIED_COLUMN_HIDDEN);
1859
- }
1860
- else {
1861
- modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
1862
- }
1863
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
1864
- fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
1865
- }
1866
- else {
1867
- fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
2154
+ })
2155
+ };
2156
+ const visibleColumns = DirListing.columns.filter(column => column.id === 'name' || !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has(column.id)));
2157
+ for (const column of visibleColumns) {
2158
+ const createElement = elementCreators[column.id];
2159
+ const element = createElement();
2160
+ element.classList.add(column.className);
2161
+ const isLastVisible = column.id === visibleColumns[visibleColumns.length - 1].id;
2162
+ if (columnsSizes) {
2163
+ const size = columnsSizes[column.id];
2164
+ if (!isLastVisible) {
2165
+ element.style.width = size + 'px';
2166
+ }
2167
+ }
2168
+ node.appendChild(element);
2169
+ if (Private.isResizable(column) && !isLastVisible) {
2170
+ const resizer = document.createElement('div');
2171
+ resizer.classList.add(RESIZE_HANDLE_CLASS);
2172
+ resizer.dataset.column = column.id;
2173
+ node.appendChild(resizer);
2174
+ }
1868
2175
  }
2176
+ const name = DOMUtils.findElement(node, NAME_ID_CLASS);
2177
+ name.classList.add(SELECTED_CLASS);
1869
2178
  // set the initial caret icon
1870
2179
  Private.updateCaret(DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS), 'right', 'up');
1871
2180
  }
@@ -1879,90 +2188,50 @@ export class DirListing extends Widget {
1879
2188
  * @returns The sort state of the header after the click event.
1880
2189
  */
1881
2190
  handleHeaderClick(node, event) {
1882
- const name = DOMUtils.findElement(node, NAME_ID_CLASS);
1883
- const modified = DOMUtils.findElement(node, MODIFIED_ID_CLASS);
1884
- const fileSize = DOMUtils.findElement(node, FILE_SIZE_ID_CLASS);
1885
2191
  const state = { direction: 'ascending', key: 'name' };
1886
2192
  const target = event.target;
1887
- const modifiedIcon = DOMUtils.findElement(modified, HEADER_ITEM_ICON_CLASS);
1888
- const fileSizeIcon = DOMUtils.findElement(fileSize, HEADER_ITEM_ICON_CLASS);
1889
- const nameIcon = DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS);
1890
- if (name.contains(target)) {
1891
- if (name.classList.contains(SELECTED_CLASS)) {
1892
- if (!name.classList.contains(DESCENDING_CLASS)) {
1893
- state.direction = 'descending';
1894
- name.classList.add(DESCENDING_CLASS);
1895
- Private.updateCaret(nameIcon, 'right', 'down');
1896
- }
1897
- else {
1898
- name.classList.remove(DESCENDING_CLASS);
1899
- Private.updateCaret(nameIcon, 'right', 'up');
1900
- }
1901
- }
1902
- else {
1903
- name.classList.remove(DESCENDING_CLASS);
1904
- Private.updateCaret(nameIcon, 'right', 'up');
2193
+ const sortableColumns = DirListing.columns.filter(Private.isSortable);
2194
+ for (const column of sortableColumns) {
2195
+ const header = node.querySelector(`.${column.className}`);
2196
+ if (!header) {
2197
+ // skip if the column is hidden
2198
+ continue;
1905
2199
  }
1906
- name.classList.add(SELECTED_CLASS);
1907
- modified.classList.remove(SELECTED_CLASS);
1908
- modified.classList.remove(DESCENDING_CLASS);
1909
- fileSize.classList.remove(SELECTED_CLASS);
1910
- fileSize.classList.remove(DESCENDING_CLASS);
1911
- Private.updateCaret(modifiedIcon, 'left');
1912
- Private.updateCaret(fileSizeIcon, 'left');
1913
- return state;
1914
- }
1915
- if (modified.contains(target)) {
1916
- state.key = 'last_modified';
1917
- if (modified.classList.contains(SELECTED_CLASS)) {
1918
- if (!modified.classList.contains(DESCENDING_CLASS)) {
1919
- state.direction = 'descending';
1920
- modified.classList.add(DESCENDING_CLASS);
1921
- Private.updateCaret(modifiedIcon, 'left', 'down');
2200
+ if (header.contains(target)) {
2201
+ state.key = column.id;
2202
+ const headerIcon = DOMUtils.findElement(header, HEADER_ITEM_ICON_CLASS);
2203
+ if (header.classList.contains(SELECTED_CLASS)) {
2204
+ if (!header.classList.contains(DESCENDING_CLASS)) {
2205
+ state.direction = 'descending';
2206
+ header.classList.add(DESCENDING_CLASS);
2207
+ Private.updateCaret(headerIcon, column.caretSide, 'down');
2208
+ }
2209
+ else {
2210
+ header.classList.remove(DESCENDING_CLASS);
2211
+ Private.updateCaret(headerIcon, column.caretSide, 'up');
2212
+ }
1922
2213
  }
1923
2214
  else {
1924
- modified.classList.remove(DESCENDING_CLASS);
1925
- Private.updateCaret(modifiedIcon, 'left', 'up');
1926
- }
1927
- }
1928
- else {
1929
- modified.classList.remove(DESCENDING_CLASS);
1930
- Private.updateCaret(modifiedIcon, 'left', 'up');
1931
- }
1932
- modified.classList.add(SELECTED_CLASS);
1933
- name.classList.remove(SELECTED_CLASS);
1934
- name.classList.remove(DESCENDING_CLASS);
1935
- fileSize.classList.remove(SELECTED_CLASS);
1936
- fileSize.classList.remove(DESCENDING_CLASS);
1937
- Private.updateCaret(nameIcon, 'right');
1938
- Private.updateCaret(fileSizeIcon, 'left');
1939
- return state;
1940
- }
1941
- if (fileSize.contains(target)) {
1942
- state.key = 'file_size';
1943
- if (fileSize.classList.contains(SELECTED_CLASS)) {
1944
- if (!fileSize.classList.contains(DESCENDING_CLASS)) {
1945
- state.direction = 'descending';
1946
- fileSize.classList.add(DESCENDING_CLASS);
1947
- Private.updateCaret(fileSizeIcon, 'left', 'down');
2215
+ header.classList.remove(DESCENDING_CLASS);
2216
+ Private.updateCaret(headerIcon, column.caretSide, 'up');
1948
2217
  }
1949
- else {
1950
- fileSize.classList.remove(DESCENDING_CLASS);
1951
- Private.updateCaret(fileSizeIcon, 'left', 'up');
2218
+ header.classList.add(SELECTED_CLASS);
2219
+ for (const otherColumn of sortableColumns) {
2220
+ if (otherColumn.id === column.id) {
2221
+ continue;
2222
+ }
2223
+ const otherHeader = node.querySelector(`.${otherColumn.className}`);
2224
+ if (!otherHeader) {
2225
+ // skip if hidden
2226
+ continue;
2227
+ }
2228
+ otherHeader.classList.remove(SELECTED_CLASS);
2229
+ otherHeader.classList.remove(DESCENDING_CLASS);
2230
+ const otherHeaderIcon = DOMUtils.findElement(otherHeader, HEADER_ITEM_ICON_CLASS);
2231
+ Private.updateCaret(otherHeaderIcon, otherColumn.caretSide);
1952
2232
  }
2233
+ return state;
1953
2234
  }
1954
- else {
1955
- fileSize.classList.remove(DESCENDING_CLASS);
1956
- Private.updateCaret(fileSizeIcon, 'left', 'up');
1957
- }
1958
- fileSize.classList.add(SELECTED_CLASS);
1959
- name.classList.remove(SELECTED_CLASS);
1960
- name.classList.remove(DESCENDING_CLASS);
1961
- modified.classList.remove(SELECTED_CLASS);
1962
- modified.classList.remove(DESCENDING_CLASS);
1963
- Private.updateCaret(nameIcon, 'right');
1964
- Private.updateCaret(modifiedIcon, 'left');
1965
- return state;
1966
2235
  }
1967
2236
  return state;
1968
2237
  }
@@ -1971,35 +2240,19 @@ export class DirListing extends Widget {
1971
2240
  *
1972
2241
  * @returns A new DOM node to use as a content item.
1973
2242
  */
1974
- createItemNode(hiddenColumns) {
2243
+ createItemNode(hiddenColumns, columnsSizes) {
1975
2244
  const node = document.createElement('li');
1976
- const icon = document.createElement('span');
1977
- const text = document.createElement('span');
1978
- const modified = document.createElement('span');
1979
- const fileSize = document.createElement('span');
1980
- icon.className = ITEM_ICON_CLASS;
1981
- text.className = ITEM_TEXT_CLASS;
1982
- modified.className = ITEM_MODIFIED_CLASS;
1983
- fileSize.className = ITEM_FILE_SIZE_CLASS;
1984
- if (!(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'))) {
1985
- const checkboxWrapper = this.createCheckboxWrapperNode();
1986
- node.appendChild(checkboxWrapper);
1987
- }
1988
- node.appendChild(icon);
1989
- node.appendChild(text);
1990
- node.appendChild(modified);
1991
- node.appendChild(fileSize);
1992
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
1993
- modified.classList.add(MODIFIED_COLUMN_HIDDEN);
1994
- }
1995
- else {
1996
- modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
1997
- }
1998
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
1999
- fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
2000
- }
2001
- else {
2002
- fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
2245
+ for (const column of DirListing.columns) {
2246
+ if (column.id != 'name' && (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has(column.id))) {
2247
+ continue;
2248
+ }
2249
+ const createElement = this.itemFactories[column.id];
2250
+ const element = createElement();
2251
+ node.appendChild(element);
2252
+ if (columnsSizes) {
2253
+ const size = columnsSizes[column.id];
2254
+ element.style.width = size + 'px';
2255
+ }
2003
2256
  }
2004
2257
  return node;
2005
2258
  }
@@ -2053,14 +2306,25 @@ export class DirListing extends Widget {
2053
2306
  * @param modifiedStyle - The date style for the modified column: narrow, short, or long
2054
2307
  */
2055
2308
  updateItemModified(modified, modifiedDate, modifiedStyle) {
2056
- let modText = '';
2057
- let modTitle = '';
2309
+ // Formatting dates is expensive (0.1-0.2ms per call,
2310
+ // so over 150 files can easily already choke the renderer),
2311
+ // let's do the bare minimum check of comparing if an update
2312
+ // is needed using a last update cache:
2313
+ const previousUpdate = this._modifiedColumnLastUpdate.get(modified);
2314
+ if ((previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.date) === modifiedDate &&
2315
+ (previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.style) === modifiedStyle) {
2316
+ return;
2317
+ }
2058
2318
  const parsedDate = new Date(modifiedDate);
2059
2319
  // Render the date in one of multiple formats, depending on the container's size
2060
- modText = Time.formatHuman(parsedDate, modifiedStyle);
2061
- modTitle = Time.format(parsedDate);
2320
+ const modText = Time.formatHuman(parsedDate, modifiedStyle);
2321
+ const modTitle = Time.format(parsedDate);
2062
2322
  modified.textContent = modText;
2063
2323
  modified.title = modTitle;
2324
+ this._modifiedColumnLastUpdate.set(modified, {
2325
+ date: modifiedDate,
2326
+ style: modifiedStyle
2327
+ });
2064
2328
  }
2065
2329
  /**
2066
2330
  * Update an item node to reflect the current state of a model.
@@ -2072,7 +2336,7 @@ export class DirListing extends Widget {
2072
2336
  * @param fileType - The file type of the item, if applicable.
2073
2337
  *
2074
2338
  */
2075
- updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle) {
2339
+ updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle, columnsSizes) {
2076
2340
  if (selected) {
2077
2341
  node.classList.add(SELECTED_CLASS);
2078
2342
  }
@@ -2083,8 +2347,9 @@ export class DirListing extends Widget {
2083
2347
  const trans = translator.load('jupyterlab');
2084
2348
  const iconContainer = DOMUtils.findElement(node, ITEM_ICON_CLASS);
2085
2349
  const text = DOMUtils.findElement(node, ITEM_TEXT_CLASS);
2086
- const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
2087
- const fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS);
2350
+ const nameColumn = DOMUtils.findElement(node, ITEM_NAME_COLUMN_CLASS);
2351
+ let modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
2352
+ let fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS);
2088
2353
  const checkboxWrapper = DOMUtils.findElement(node, CHECKBOX_WRAPPER_CLASS);
2089
2354
  const showFileCheckboxes = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'));
2090
2355
  if (checkboxWrapper && !showFileCheckboxes) {
@@ -2092,19 +2357,23 @@ export class DirListing extends Widget {
2092
2357
  }
2093
2358
  else if (showFileCheckboxes && !checkboxWrapper) {
2094
2359
  const checkboxWrapper = this.createCheckboxWrapperNode();
2095
- node.insertBefore(checkboxWrapper, iconContainer);
2360
+ nameColumn.insertAdjacentElement('beforebegin', checkboxWrapper);
2096
2361
  }
2097
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
2098
- modified.classList.add(MODIFIED_COLUMN_HIDDEN);
2362
+ const showModified = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified'));
2363
+ if (modified && !showModified) {
2364
+ node.removeChild(modified);
2099
2365
  }
2100
- else {
2101
- modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
2366
+ else if (showModified && !modified) {
2367
+ modified = this.itemFactories.last_modified();
2368
+ nameColumn.insertAdjacentElement('afterend', modified);
2102
2369
  }
2103
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
2104
- fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
2370
+ const showFileSize = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size'));
2371
+ if (fileSize && !showFileSize) {
2372
+ node.removeChild(fileSize);
2105
2373
  }
2106
- else {
2107
- fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
2374
+ else if (showFileSize && !fileSize) {
2375
+ fileSize = this.itemFactories.file_size();
2376
+ (modified !== null && modified !== void 0 ? modified : nameColumn).insertAdjacentElement('afterend', fileSize);
2108
2377
  }
2109
2378
  // render the file item's icon
2110
2379
  LabIcon.resolveElement({
@@ -2118,10 +2387,12 @@ export class DirListing extends Widget {
2118
2387
  // add file size to pop up if its available
2119
2388
  if (model.size !== null && model.size !== undefined) {
2120
2389
  const fileSizeText = Private.formatFileSize(model.size, 1, 1024);
2121
- fileSize.textContent = fileSizeText;
2390
+ if (fileSize) {
2391
+ fileSize.textContent = fileSizeText;
2392
+ }
2122
2393
  hoverText += trans.__('\nSize: %1', Private.formatFileSize(model.size, 1, 1024));
2123
2394
  }
2124
- else {
2395
+ else if (fileSize) {
2125
2396
  fileSize.textContent = '';
2126
2397
  }
2127
2398
  if (model.path) {
@@ -2171,7 +2442,27 @@ export class DirListing extends Widget {
2171
2442
  checkbox.setAttribute('aria-label', ariaLabel);
2172
2443
  checkbox.checked = selected !== null && selected !== void 0 ? selected : false;
2173
2444
  }
2174
- if (model.last_modified) {
2445
+ this.updateItemSize(node, model, modifiedStyle, columnsSizes);
2446
+ }
2447
+ /**
2448
+ * Update size of item nodes, assuming that model has not changed.
2449
+ */
2450
+ updateItemSize(node, model, modifiedStyle, columnsSizes) {
2451
+ if (columnsSizes) {
2452
+ for (const column of DirListing.columns) {
2453
+ const element = DOMUtils.findElement(node, column.itemClassName);
2454
+ if (!element) {
2455
+ continue;
2456
+ }
2457
+ const sizeSpec = columnsSizes[column.id];
2458
+ const newWidth = sizeSpec === null ? '' : sizeSpec + 'px';
2459
+ if (newWidth !== element.style.width) {
2460
+ element.style.width = newWidth;
2461
+ }
2462
+ }
2463
+ }
2464
+ let modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
2465
+ if (model.last_modified && modified) {
2175
2466
  this.updateItemModified(modified, model.last_modified, modifiedStyle !== null && modifiedStyle !== void 0 ? modifiedStyle : 'short');
2176
2467
  }
2177
2468
  }
@@ -2381,6 +2672,18 @@ var Private;
2381
2672
  return copy;
2382
2673
  }
2383
2674
  Private.sort = sort;
2675
+ /**
2676
+ * Check if the column is resizable.
2677
+ */
2678
+ Private.isResizable = (column) => {
2679
+ return 'resizable' in column && column.resizable;
2680
+ };
2681
+ /**
2682
+ * Check if the column is sortable.
2683
+ */
2684
+ Private.isSortable = (column) => {
2685
+ return 'sortable' in column && column.sortable;
2686
+ };
2384
2687
  /**
2385
2688
  * Get the index of the node at a client position, or `-1`.
2386
2689
  */
@@ -2416,9 +2719,14 @@ var Private;
2416
2719
  (state === 'down' ? caretDownIcon : caretUpIcon).element({
2417
2720
  container,
2418
2721
  tag: 'span',
2419
- stylesheet: 'listingHeaderItem',
2420
- float
2722
+ stylesheet: 'listingHeaderItem'
2421
2723
  });
2724
+ if (float === 'left') {
2725
+ container.style.order = '-1';
2726
+ }
2727
+ else {
2728
+ container.style.order = '';
2729
+ }
2422
2730
  }
2423
2731
  else {
2424
2732
  LabIcon.remove(container);
@@ -2426,5 +2734,66 @@ var Private;
2426
2734
  }
2427
2735
  }
2428
2736
  Private.updateCaret = updateCaret;
2737
+ async function createDirectory(manager, path, name) {
2738
+ const model = await manager.newUntitled({
2739
+ path: path,
2740
+ type: 'directory'
2741
+ });
2742
+ const tmpDirPath = PathExt.join(path, model.name);
2743
+ const dirPath = PathExt.join(path, name);
2744
+ try {
2745
+ await manager.rename(tmpDirPath, dirPath);
2746
+ }
2747
+ catch (e) {
2748
+ // The `dirPath` already exists, remove the temporary new directory
2749
+ await manager.deleteFile(tmpDirPath);
2750
+ }
2751
+ return dirPath;
2752
+ }
2753
+ Private.createDirectory = createDirectory;
2754
+ function isDirectoryEntry(entry) {
2755
+ return entry.isDirectory;
2756
+ }
2757
+ Private.isDirectoryEntry = isDirectoryEntry;
2758
+ function isFileEntry(entry) {
2759
+ return entry.isFile;
2760
+ }
2761
+ Private.isFileEntry = isFileEntry;
2762
+ function defensiveGetAsEntry(item) {
2763
+ if (item.webkitGetAsEntry) {
2764
+ return item.webkitGetAsEntry();
2765
+ }
2766
+ if ('getAsEntry' in item) {
2767
+ // See https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
2768
+ return item['getAsEntry']();
2769
+ }
2770
+ return null;
2771
+ }
2772
+ Private.defensiveGetAsEntry = defensiveGetAsEntry;
2773
+ function readEntries(reader) {
2774
+ return new Promise((resolve, reject) => reader.readEntries(resolve, reject));
2775
+ }
2776
+ function readFile(entry) {
2777
+ return new Promise((resolve, reject) => entry.file(resolve, reject));
2778
+ }
2779
+ Private.readFile = readFile;
2780
+ async function collectEntries(reader) {
2781
+ // Spec requires calling `readEntries` until these are exhausted;
2782
+ // in practice this is only required in Chromium-based browsers for >100 files.
2783
+ // https://issues.chromium.org/issues/41110876
2784
+ const allEntries = [];
2785
+ let done = false;
2786
+ while (!done) {
2787
+ const entries = await readEntries(reader);
2788
+ if (entries.length === 0) {
2789
+ done = true;
2790
+ }
2791
+ else {
2792
+ allEntries.push(...entries);
2793
+ }
2794
+ }
2795
+ return allEntries;
2796
+ }
2797
+ Private.collectEntries = collectEntries;
2429
2798
  })(Private || (Private = {}));
2430
2799
  //# sourceMappingURL=listing.js.map