@jupyterlab/filebrowser 4.3.0-alpha.2 → 4.3.0-beta.0

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,15 @@ 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
+ return this.renderer.updateItemSize(node, item, this._modifiedStyle, this._columnSizes);
798
+ }
735
799
  const ft = this._manager.registry.getFileTypeForModel(item);
736
- this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle);
800
+ this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle, this._columnSizes);
737
801
  if (this.selection[item.path] &&
738
802
  this._isCut &&
739
803
  this._model.path === this._prevPath) {
@@ -786,7 +850,7 @@ export class DirListing extends Widget {
786
850
  }
787
851
  // Add any missing item nodes.
788
852
  while (nodes.length < items.length) {
789
- const node = renderer.createItemNode(this._hiddenColumns);
853
+ const node = renderer.createItemNode(this._hiddenColumns, this._columnSizes);
790
854
  node.classList.add(ITEM_CLASS);
791
855
  nodes.push(node);
792
856
  content.appendChild(node);
@@ -833,14 +897,8 @@ export class DirListing extends Widget {
833
897
  }
834
898
  onResize(msg) {
835
899
  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
- }
900
+ this._width = width;
901
+ this._updateColumnSizes();
844
902
  }
845
903
  setColumnVisibility(name, visible) {
846
904
  if (visible) {
@@ -850,7 +908,99 @@ export class DirListing extends Widget {
850
908
  this._hiddenColumns.add(name);
851
909
  }
852
910
  this.headerNode.innerHTML = '';
853
- this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns);
911
+ this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns, this._columnSizes);
912
+ this._updateColumnSizes();
913
+ }
914
+ _updateColumnSizes() {
915
+ // adjust column sizes so that they add up to the total width available, preserving ratios
916
+ // and removing width from the last column so that it fills the width available;
917
+ const visibleColumns = this._visibleColumns.map(column => ({
918
+ ...column,
919
+ element: DOMUtils.findElement(this.node, column.className)
920
+ }));
921
+ // read from DOM
922
+ let total = 0;
923
+ for (const column of visibleColumns) {
924
+ let size = this._columnSizes[column.id];
925
+ if (size === null) {
926
+ size = column.element.getBoundingClientRect().width;
927
+ }
928
+ // restrict the minimum and maximum width
929
+ size = Math.max(size, column.minWidth);
930
+ if (this._width) {
931
+ let reservedForOtherColums = 0;
932
+ for (const other of visibleColumns) {
933
+ if (other.id === column.id) {
934
+ continue;
935
+ }
936
+ reservedForOtherColums += other.minWidth;
937
+ }
938
+ size = Math.min(size, this._width - reservedForOtherColums);
939
+ }
940
+ this._columnSizes[column.id] = size;
941
+ total += size;
942
+ }
943
+ // Ensure that total fits
944
+ if (this._width && total > this._width) {
945
+ for (const column of visibleColumns) {
946
+ this._columnSizes[column.id] =
947
+ (this._columnSizes[column.id] / total) * this._width;
948
+ }
949
+ }
950
+ // Write to DOM
951
+ for (const column of visibleColumns) {
952
+ const size = this._columnSizes[column.id];
953
+ column.element.style.width = size === null ? '' : size + 'px';
954
+ }
955
+ this._updateModifiedStyleAndSize();
956
+ // Refresh sizes on the per item widths
957
+ if (this.isVisible) {
958
+ const items = this._items;
959
+ if (items.length !== 0) {
960
+ this.updateNodes(this._sortedItems, this._items, true);
961
+ }
962
+ }
963
+ if (this._state && this._stateColumnsKey) {
964
+ void this._state.save(this._stateColumnsKey, {
965
+ sizes: this._columnSizes
966
+ });
967
+ }
968
+ }
969
+ get _visibleColumns() {
970
+ return DirListing.columns.filter(column => { var _a; return column.id === 'name' || !((_a = this._hiddenColumns) === null || _a === void 0 ? void 0 : _a.has(column.id)); });
971
+ }
972
+ _setColumnSize(name, size) {
973
+ var _a;
974
+ const previousSize = this._columnSizes[name];
975
+ if (previousSize && size && size > previousSize) {
976
+ // check if we can resize up
977
+ let total = 0;
978
+ let before = true;
979
+ for (const column of this._visibleColumns) {
980
+ if (column.id === name) {
981
+ // add proposed size for the current columns
982
+ total += size;
983
+ before = false;
984
+ continue;
985
+ }
986
+ if (before) {
987
+ // add size as-is for columns before
988
+ const element = DOMUtils.findElement(this.node, column.className);
989
+ total +=
990
+ (_a = this._columnSizes[column.id]) !== null && _a !== void 0 ? _a : element.getBoundingClientRect().width;
991
+ }
992
+ else {
993
+ // add minimum acceptable size for columns after
994
+ total += column.minWidth;
995
+ }
996
+ }
997
+ if (this._width && total > this._width) {
998
+ // up sizing is no longer possible
999
+ return;
1000
+ }
1001
+ }
1002
+ this._columnSizes[name] = size;
1003
+ this._updateColumnSizes();
854
1004
  }
855
1005
  /**
856
1006
  * Update the setting to sort notebooks above files.
@@ -863,6 +1013,13 @@ export class DirListing extends Widget {
863
1013
  this.sort(this._sortState);
864
1014
  }
865
1015
  }
1016
+ /**
1017
+ * Update the setting to allow single click navigation.
1018
+ * This enables opening files/directories with a single click.
1019
+ */
1020
+ setAllowSingleClickNavigation(isEnabled) {
1021
+ this._allowSingleClick = isEnabled;
1022
+ }
866
1023
  /**
867
1024
  * Would this click (or other event type) hit the checkbox by default?
868
1025
  */
@@ -945,6 +1102,36 @@ export class DirListing extends Widget {
945
1102
  }
946
1103
  let index = Private.hitTestNodes(this._items, event);
947
1104
  if (index === -1) {
1105
+ // Left mouse press for drag or resize start.
1106
+ if (event.button === 0) {
1107
+ const resizeHandle = event.target;
1108
+ if (resizeHandle instanceof HTMLElement &&
1109
+ resizeHandle.classList.contains(RESIZE_HANDLE_CLASS)) {
1110
+ const columnId = resizeHandle.dataset.column;
1111
+ if (!columnId) {
1112
+ throw Error('Column resize handle is missing data-column attribute');
1113
+ }
1114
+ const column = DirListing.columns.find(c => c.id === columnId);
1115
+ if (!column) {
1116
+ throw Error(`Column with identifier ${columnId} not found`);
1117
+ }
1118
+ const element = DOMUtils.findElement(this.node, column.className);
1119
+ resizeHandle.classList.add(ACTIVE_CLASS);
1120
+ const cursorOverride = Drag.overrideCursor('col-resize');
1121
+ this._resizeData = {
1122
+ pressX: event.clientX,
1123
+ column: columnId,
1124
+ initialSize: element.getBoundingClientRect().width,
1125
+ overrides: new DisposableDelegate(() => {
1126
+ cursorOverride.dispose();
1127
+ resizeHandle.classList.remove(ACTIVE_CLASS);
1128
+ })
1129
+ };
1130
+ document.addEventListener('mouseup', this, true);
1131
+ document.addEventListener('mousemove', this, true);
1132
+ return;
1133
+ }
1134
+ }
948
1135
  return;
949
1136
  }
950
1137
  this.handleFileSelect(event);
@@ -956,7 +1143,7 @@ export class DirListing extends Widget {
956
1143
  if (newContext) {
957
1144
  return;
958
1145
  }
959
- // Left mouse press for drag start.
1146
+ // Left mouse press for drag or resize start.
960
1147
  if (event.button === 0) {
961
1148
  this._dragData = {
962
1149
  pressX: event.clientX,
@@ -966,6 +1153,9 @@ export class DirListing extends Widget {
966
1153
  document.addEventListener('mouseup', this, true);
967
1154
  document.addEventListener('mousemove', this, true);
968
1155
  }
1156
+ if (this._allowSingleClick) {
1157
+ this.evtDblClick(event);
1158
+ }
969
1159
  }
970
1160
  /**
971
1161
  * Handle the `'mouseup'` event for the widget.
@@ -988,6 +1178,13 @@ export class DirListing extends Widget {
988
1178
  if (event.button === 0) {
989
1179
  this._focusItem(this._focusIndex);
990
1180
  }
1181
+ // Remove the resize listeners if necessary.
1182
+ if (this._resizeData) {
1183
+ this._resizeData.overrides.dispose();
1184
+ document.removeEventListener('mousemove', this, true);
1185
+ document.removeEventListener('mouseup', this, true);
1186
+ return;
1187
+ }
991
1188
  // Remove the drag listeners if necessary.
992
1189
  if (event.button !== 0 || !this._drag) {
993
1190
  document.removeEventListener('mousemove', this, true);
@@ -1003,6 +1200,11 @@ export class DirListing extends Widget {
1003
1200
  _evtMousemove(event) {
1004
1201
  event.preventDefault();
1005
1202
  event.stopPropagation();
1203
+ if (this._resizeData) {
1204
+ const { initialSize, column, pressX } = this._resizeData;
1205
+ this._setColumnSize(column, initialSize + event.clientX - pressX);
1206
+ return;
1207
+ }
1006
1208
  // Bail if we are the one dragging.
1007
1209
  if (this._drag || !this._dragData) {
1008
1210
  return;
@@ -1245,29 +1447,61 @@ export class DirListing extends Widget {
1245
1447
  */
1246
1448
  evtNativeDrop(event) {
1247
1449
  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) {
1450
+ // Prevent navigation
1451
+ event.preventDefault();
1452
+ const items = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.items;
1453
+ if (!items) {
1454
+ // Fallback to simple upload of files (if any)
1455
+ const files = (_b = event.dataTransfer) === null || _b === void 0 ? void 0 : _b.files;
1456
+ if (!files || files.length === 0) {
1457
+ return;
1458
+ }
1459
+ const promises = [];
1460
+ for (const file of files) {
1461
+ const promise = this._model.upload(file);
1462
+ promises.push(promise);
1463
+ }
1464
+ Promise.all(promises)
1465
+ .then(() => this._allUploaded.emit())
1466
+ .catch(err => {
1467
+ console.error('Error while uploading files: ', err);
1468
+ });
1254
1469
  return;
1255
1470
  }
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
- });
1471
+ const uploadEntry = async (entry, path) => {
1472
+ if (Private.isDirectoryEntry(entry)) {
1473
+ const dirPath = await Private.createDirectory(this._model.manager, path, entry.name);
1474
+ const directoryReader = entry.createReader();
1475
+ const allEntries = await Private.collectEntries(directoryReader);
1476
+ for (const childEntry of allEntries) {
1477
+ await uploadEntry(childEntry, dirPath);
1478
+ }
1265
1479
  }
1480
+ else if (Private.isFileEntry(entry)) {
1481
+ const file = await Private.readFile(entry);
1482
+ await this._model.upload(file, path);
1483
+ }
1484
+ };
1485
+ const promises = [];
1486
+ for (const item of items) {
1487
+ const entry = Private.defensiveGetAsEntry(item);
1488
+ if (!entry) {
1489
+ continue;
1490
+ }
1491
+ const promise = uploadEntry(entry, (_c = this._model.path) !== null && _c !== void 0 ? _c : '/');
1492
+ promises.push(promise);
1266
1493
  }
1267
- event.preventDefault();
1268
- for (let i = 0; i < files.length; i++) {
1269
- void this._model.upload(files[i]);
1270
- }
1494
+ Promise.all(promises)
1495
+ .then(() => this._allUploaded.emit())
1496
+ .catch(err => {
1497
+ console.error('Error while uploading files: ', err);
1498
+ });
1499
+ }
1500
+ /**
1501
+ * Signal emitted on when all files were uploaded after native drag.
1502
+ */
1503
+ get allUploaded() {
1504
+ return this._allUploaded;
1271
1505
  }
1272
1506
  /**
1273
1507
  * Handle the `'lm-dragenter'` event for the widget.
@@ -1801,10 +2035,83 @@ export class DirListing extends Widget {
1801
2035
  * The namespace for the `DirListing` class statics.
1802
2036
  */
1803
2037
  (function (DirListing) {
2038
+ /**
2039
+ * Column definitions.
2040
+ */
2041
+ DirListing.columns = [
2042
+ {
2043
+ id: 'is_selected',
2044
+ className: CHECKBOX_WRAPPER_CLASS,
2045
+ itemClassName: CHECKBOX_WRAPPER_CLASS,
2046
+ minWidth: 18,
2047
+ resizable: false,
2048
+ sortable: false
2049
+ },
2050
+ {
2051
+ id: 'name',
2052
+ className: NAME_ID_CLASS,
2053
+ itemClassName: ITEM_NAME_COLUMN_CLASS,
2054
+ minWidth: 60,
2055
+ resizable: true,
2056
+ sortable: true,
2057
+ caretSide: 'right'
2058
+ },
2059
+ {
2060
+ id: 'last_modified',
2061
+ className: MODIFIED_ID_CLASS,
2062
+ itemClassName: ITEM_MODIFIED_CLASS,
2063
+ minWidth: 60,
2064
+ resizable: true,
2065
+ sortable: true,
2066
+ caretSide: 'left'
2067
+ },
2068
+ {
2069
+ id: 'file_size',
2070
+ className: FILE_SIZE_ID_CLASS,
2071
+ itemClassName: ITEM_FILE_SIZE_CLASS,
2072
+ minWidth: 60,
2073
+ resizable: true,
2074
+ sortable: true,
2075
+ caretSide: 'left'
2076
+ }
2077
+ ];
1804
2078
  /**
1805
2079
  * The default implementation of an `IRenderer`.
1806
2080
  */
1807
2081
  class Renderer {
2082
+ constructor() {
2083
+ /**
2084
+ * Factories for individual parts of the item.
2085
+ */
2086
+ this.itemFactories = {
2087
+ name: () => {
2088
+ const name = document.createElement('span');
2089
+ const icon = document.createElement('span');
2090
+ const text = document.createElement('span');
2091
+ icon.className = ITEM_ICON_CLASS;
2092
+ text.className = ITEM_TEXT_CLASS;
2093
+ name.className = ITEM_NAME_COLUMN_CLASS;
2094
+ name.appendChild(icon);
2095
+ name.appendChild(text);
2096
+ return name;
2097
+ },
2098
+ last_modified: () => {
2099
+ const modified = document.createElement('span');
2100
+ modified.className = ITEM_MODIFIED_CLASS;
2101
+ return modified;
2102
+ },
2103
+ file_size: () => {
2104
+ const fileSize = document.createElement('span');
2105
+ fileSize.className = ITEM_FILE_SIZE_CLASS;
2106
+ return fileSize;
2107
+ },
2108
+ is_selected: () => this.createCheckboxWrapperNode()
2109
+ };
2110
+ /**
2111
+ * Register of most recent arguments for last modified column update.
2112
+ */
2113
+ this._modifiedColumnLastUpdate = new WeakMap();
2114
+ }
1808
2115
  /**
1809
2116
  * Create the DOM node for a dir listing.
1810
2117
  */
@@ -1827,45 +2134,43 @@ export class DirListing extends Widget {
1827
2134
  *
1828
2135
  * @param node - The header node to populate.
1829
2136
  */
1830
- populateHeaderNode(node, translator, hiddenColumns) {
2137
+ populateHeaderNode(node, translator, hiddenColumns, columnsSizes) {
1831
2138
  translator = translator || nullTranslator;
1832
2139
  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({
2140
+ const elementCreators = {
2141
+ name: () => this.createHeaderItemNode(trans.__('Name')),
2142
+ last_modified: () => this._createHeaderItemNodeWithSizes({
2143
+ small: trans.__('Modified'),
2144
+ large: trans.__('Last Modified')
2145
+ }),
2146
+ file_size: () => this.createHeaderItemNode(trans.__('File Size')),
2147
+ is_selected: () => this.createCheckboxWrapperNode({
1848
2148
  alwaysVisible: true,
1849
2149
  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);
2150
+ })
2151
+ };
2152
+ const visibleColumns = DirListing.columns.filter(column => column.id === 'name' || !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has(column.id)));
2153
+ for (const column of visibleColumns) {
2154
+ const createElement = elementCreators[column.id];
2155
+ const element = createElement();
2156
+ element.classList.add(column.className);
2157
+ const isLastVisible = column.id === visibleColumns[visibleColumns.length - 1].id;
2158
+ if (columnsSizes) {
2159
+ const size = columnsSizes[column.id];
2160
+ if (!isLastVisible) {
2161
+ element.style.width = size + 'px';
2162
+ }
2163
+ }
2164
+ node.appendChild(element);
2165
+ if (Private.isResizable(column) && !isLastVisible) {
2166
+ const resizer = document.createElement('div');
2167
+ resizer.classList.add(RESIZE_HANDLE_CLASS);
2168
+ resizer.dataset.column = column.id;
2169
+ node.appendChild(resizer);
2170
+ }
1868
2171
  }
2172
+ const name = DOMUtils.findElement(node, NAME_ID_CLASS);
2173
+ name.classList.add(SELECTED_CLASS);
1869
2174
  // set the initial caret icon
1870
2175
  Private.updateCaret(DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS), 'right', 'up');
1871
2176
  }
@@ -1879,90 +2184,51 @@ export class DirListing extends Widget {
1879
2184
  * @returns The sort state of the header after the click event.
1880
2185
  */
1881
2186
  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
2187
  const state = { direction: 'ascending', key: 'name' };
1886
2188
  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');
2189
+ const sortableColumns = DirListing.columns.filter(Private.isSortable);
2190
+ console.log(sortableColumns);
2191
+ for (const column of sortableColumns) {
2192
+ const header = node.querySelector(`.${column.className}`);
2193
+ if (!header) {
2194
+ // skip if the column is hidden
2195
+ continue;
1905
2196
  }
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');
2197
+ if (header.contains(target)) {
2198
+ state.key = column.id;
2199
+ const headerIcon = DOMUtils.findElement(header, HEADER_ITEM_ICON_CLASS);
2200
+ if (header.classList.contains(SELECTED_CLASS)) {
2201
+ if (!header.classList.contains(DESCENDING_CLASS)) {
2202
+ state.direction = 'descending';
2203
+ header.classList.add(DESCENDING_CLASS);
2204
+ Private.updateCaret(headerIcon, column.caretSide, 'down');
2205
+ }
2206
+ else {
2207
+ header.classList.remove(DESCENDING_CLASS);
2208
+ Private.updateCaret(headerIcon, column.caretSide, 'up');
2209
+ }
1922
2210
  }
1923
2211
  else {
1924
- modified.classList.remove(DESCENDING_CLASS);
1925
- Private.updateCaret(modifiedIcon, 'left', 'up');
2212
+ header.classList.remove(DESCENDING_CLASS);
2213
+ Private.updateCaret(headerIcon, column.caretSide, 'up');
1926
2214
  }
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.add(SELECTED_CLASS);
2216
+ for (const otherColumn of sortableColumns) {
2217
+ if (otherColumn.id === column.id) {
2218
+ continue;
2219
+ }
2220
+ const otherHeader = node.querySelector(`.${otherColumn.className}`);
2221
+ if (!otherHeader) {
2222
+ // skip if hidden
2223
+ continue;
2224
+ }
2225
+ otherHeader.classList.remove(SELECTED_CLASS);
2226
+ otherHeader.classList.remove(DESCENDING_CLASS);
2227
+ const otherHeaderIcon = DOMUtils.findElement(otherHeader, HEADER_ITEM_ICON_CLASS);
2228
+ Private.updateCaret(otherHeaderIcon, otherColumn.caretSide);
1948
2229
  }
1949
- else {
1950
- fileSize.classList.remove(DESCENDING_CLASS);
1951
- Private.updateCaret(fileSizeIcon, 'left', 'up');
1952
- }
1953
- }
1954
- else {
1955
- fileSize.classList.remove(DESCENDING_CLASS);
1956
- Private.updateCaret(fileSizeIcon, 'left', 'up');
2230
+ return state;
1957
2231
  }
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
2232
  }
1967
2233
  return state;
1968
2234
  }
@@ -1971,35 +2237,19 @@ export class DirListing extends Widget {
1971
2237
  *
1972
2238
  * @returns A new DOM node to use as a content item.
1973
2239
  */
1974
- createItemNode(hiddenColumns) {
2240
+ createItemNode(hiddenColumns, columnsSizes) {
1975
2241
  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);
2242
+ for (const column of DirListing.columns) {
2243
+ if (column.id != 'name' && (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has(column.id))) {
2244
+ continue;
2245
+ }
2246
+ const createElement = this.itemFactories[column.id];
2247
+ const element = createElement();
2248
+ node.appendChild(element);
2249
+ if (columnsSizes) {
2250
+ const size = columnsSizes[column.id];
2251
+ element.style.width = size + 'px';
2252
+ }
2003
2253
  }
2004
2254
  return node;
2005
2255
  }
@@ -2053,14 +2303,25 @@ export class DirListing extends Widget {
2053
2303
  * @param modifiedStyle - The date style for the modified column: narrow, short, or long
2054
2304
  */
2055
2305
  updateItemModified(modified, modifiedDate, modifiedStyle) {
2056
- let modText = '';
2057
- let modTitle = '';
2306
+ // Formatting dates is expensive (0.1-0.2ms per call,
2307
+ // so over 150 files can easily already choke the renderer),
2308
+ // let's do the bare minimum check of comparing if an update
2309
+ // is needed using a last update cache:
2310
+ const previousUpdate = this._modifiedColumnLastUpdate.get(modified);
2311
+ if ((previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.date) === modifiedDate &&
2312
+ (previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.style) === modifiedStyle) {
2313
+ return;
2314
+ }
2058
2315
  const parsedDate = new Date(modifiedDate);
2059
2316
  // 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);
2317
+ const modText = Time.formatHuman(parsedDate, modifiedStyle);
2318
+ const modTitle = Time.format(parsedDate);
2062
2319
  modified.textContent = modText;
2063
2320
  modified.title = modTitle;
2321
+ this._modifiedColumnLastUpdate.set(modified, {
2322
+ date: modifiedDate,
2323
+ style: modifiedStyle
2324
+ });
2064
2325
  }
2065
2326
  /**
2066
2327
  * Update an item node to reflect the current state of a model.
@@ -2072,7 +2333,7 @@ export class DirListing extends Widget {
2072
2333
  * @param fileType - The file type of the item, if applicable.
2073
2334
  *
2074
2335
  */
2075
- updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle) {
2336
+ updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle, columnsSizes) {
2076
2337
  if (selected) {
2077
2338
  node.classList.add(SELECTED_CLASS);
2078
2339
  }
@@ -2083,8 +2344,9 @@ export class DirListing extends Widget {
2083
2344
  const trans = translator.load('jupyterlab');
2084
2345
  const iconContainer = DOMUtils.findElement(node, ITEM_ICON_CLASS);
2085
2346
  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);
2347
+ const nameColumn = DOMUtils.findElement(node, ITEM_NAME_COLUMN_CLASS);
2348
+ let modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
2349
+ let fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS);
2088
2350
  const checkboxWrapper = DOMUtils.findElement(node, CHECKBOX_WRAPPER_CLASS);
2089
2351
  const showFileCheckboxes = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'));
2090
2352
  if (checkboxWrapper && !showFileCheckboxes) {
@@ -2092,19 +2354,23 @@ export class DirListing extends Widget {
2092
2354
  }
2093
2355
  else if (showFileCheckboxes && !checkboxWrapper) {
2094
2356
  const checkboxWrapper = this.createCheckboxWrapperNode();
2095
- node.insertBefore(checkboxWrapper, iconContainer);
2357
+ nameColumn.insertAdjacentElement('beforebegin', checkboxWrapper);
2096
2358
  }
2097
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified')) {
2098
- modified.classList.add(MODIFIED_COLUMN_HIDDEN);
2359
+ const showModified = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified'));
2360
+ if (modified && !showModified) {
2361
+ node.removeChild(modified);
2099
2362
  }
2100
- else {
2101
- modified.classList.remove(MODIFIED_COLUMN_HIDDEN);
2363
+ else if (showModified && !modified) {
2364
+ modified = this.itemFactories.last_modified();
2365
+ nameColumn.insertAdjacentElement('afterend', modified);
2102
2366
  }
2103
- if (hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size')) {
2104
- fileSize.classList.add(FILE_SIZE_COLUMN_HIDDEN);
2367
+ const showFileSize = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size'));
2368
+ if (fileSize && !showFileSize) {
2369
+ node.removeChild(fileSize);
2105
2370
  }
2106
- else {
2107
- fileSize.classList.remove(FILE_SIZE_COLUMN_HIDDEN);
2371
+ else if (showFileSize && !fileSize) {
2372
+ fileSize = this.itemFactories.file_size();
2373
+ (modified !== null && modified !== void 0 ? modified : nameColumn).insertAdjacentElement('afterend', fileSize);
2108
2374
  }
2109
2375
  // render the file item's icon
2110
2376
  LabIcon.resolveElement({
@@ -2118,10 +2384,12 @@ export class DirListing extends Widget {
2118
2384
  // add file size to pop up if its available
2119
2385
  if (model.size !== null && model.size !== undefined) {
2120
2386
  const fileSizeText = Private.formatFileSize(model.size, 1, 1024);
2121
- fileSize.textContent = fileSizeText;
2387
+ if (fileSize) {
2388
+ fileSize.textContent = fileSizeText;
2389
+ }
2122
2390
  hoverText += trans.__('\nSize: %1', Private.formatFileSize(model.size, 1, 1024));
2123
2391
  }
2124
- else {
2392
+ else if (fileSize) {
2125
2393
  fileSize.textContent = '';
2126
2394
  }
2127
2395
  if (model.path) {
@@ -2171,7 +2439,27 @@ export class DirListing extends Widget {
2171
2439
  checkbox.setAttribute('aria-label', ariaLabel);
2172
2440
  checkbox.checked = selected !== null && selected !== void 0 ? selected : false;
2173
2441
  }
2174
- if (model.last_modified) {
2442
+ this.updateItemSize(node, model, modifiedStyle, columnsSizes);
2443
+ }
2444
+ /**
2445
+ * Update size of item nodes, assuming that model has not changed.
2446
+ */
2447
+ updateItemSize(node, model, modifiedStyle, columnsSizes) {
2448
+ if (columnsSizes) {
2449
+ for (const column of DirListing.columns) {
2450
+ const element = DOMUtils.findElement(node, column.itemClassName);
2451
+ if (!element) {
2452
+ continue;
2453
+ }
2454
+ const sizeSpec = columnsSizes[column.id];
2455
+ const newWidth = sizeSpec === null ? '' : sizeSpec + 'px';
2456
+ if (newWidth !== element.style.width) {
2457
+ element.style.width = newWidth;
2458
+ }
2459
+ }
2460
+ }
2461
+ let modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
2462
+ if (model.last_modified && modified) {
2175
2463
  this.updateItemModified(modified, model.last_modified, modifiedStyle !== null && modifiedStyle !== void 0 ? modifiedStyle : 'short');
2176
2464
  }
2177
2465
  }
@@ -2381,6 +2669,18 @@ var Private;
2381
2669
  return copy;
2382
2670
  }
2383
2671
  Private.sort = sort;
2672
+ /**
2673
+ * Check if the column is resizable.
2674
+ */
2675
+ Private.isResizable = (column) => {
2676
+ return 'resizable' in column && column.resizable;
2677
+ };
2678
+ /**
2679
+ * Check if the column is sortable.
2680
+ */
2681
+ Private.isSortable = (column) => {
2682
+ return 'sortable' in column && column.sortable;
2683
+ };
2384
2684
  /**
2385
2685
  * Get the index of the node at a client position, or `-1`.
2386
2686
  */
@@ -2416,9 +2716,14 @@ var Private;
2416
2716
  (state === 'down' ? caretDownIcon : caretUpIcon).element({
2417
2717
  container,
2418
2718
  tag: 'span',
2419
- stylesheet: 'listingHeaderItem',
2420
- float
2719
+ stylesheet: 'listingHeaderItem'
2421
2720
  });
2721
+ if (float === 'left') {
2722
+ container.style.order = '-1';
2723
+ }
2724
+ else {
2725
+ container.style.order = '';
2726
+ }
2422
2727
  }
2423
2728
  else {
2424
2729
  LabIcon.remove(container);
@@ -2426,5 +2731,66 @@ var Private;
2426
2731
  }
2427
2732
  }
2428
2733
  Private.updateCaret = updateCaret;
2734
+ async function createDirectory(manager, path, name) {
2735
+ const model = await manager.newUntitled({
2736
+ path: path,
2737
+ type: 'directory'
2738
+ });
2739
+ const tmpDirPath = PathExt.join(path, model.name);
2740
+ const dirPath = PathExt.join(path, name);
2741
+ try {
2742
+ await manager.rename(tmpDirPath, dirPath);
2743
+ }
2744
+ catch (e) {
2745
+ // The `dirPath` already exists, remove the temporary new directory
2746
+ await manager.deleteFile(tmpDirPath);
2747
+ }
2748
+ return dirPath;
2749
+ }
2750
+ Private.createDirectory = createDirectory;
2751
+ function isDirectoryEntry(entry) {
2752
+ return entry.isDirectory;
2753
+ }
2754
+ Private.isDirectoryEntry = isDirectoryEntry;
2755
+ function isFileEntry(entry) {
2756
+ return entry.isFile;
2757
+ }
2758
+ Private.isFileEntry = isFileEntry;
2759
+ function defensiveGetAsEntry(item) {
2760
+ if (item.webkitGetAsEntry) {
2761
+ return item.webkitGetAsEntry();
2762
+ }
2763
+ if ('getAsEntry' in item) {
2764
+ // See https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
2765
+ return item['getAsEntry']();
2766
+ }
2767
+ return null;
2768
+ }
2769
+ Private.defensiveGetAsEntry = defensiveGetAsEntry;
2770
+ function readEntries(reader) {
2771
+ return new Promise((resolve, reject) => reader.readEntries(resolve, reject));
2772
+ }
2773
+ function readFile(entry) {
2774
+ return new Promise((resolve, reject) => entry.file(resolve, reject));
2775
+ }
2776
+ Private.readFile = readFile;
2777
+ async function collectEntries(reader) {
2778
+ // Spec requires calling `readEntries` until these are exhausted;
2779
+ // in practice this is only required in Chromium-based browsers for >100 files.
2780
+ // https://issues.chromium.org/issues/41110876
2781
+ const allEntries = [];
2782
+ let done = false;
2783
+ while (!done) {
2784
+ const entries = await readEntries(reader);
2785
+ if (entries.length === 0) {
2786
+ done = true;
2787
+ }
2788
+ else {
2789
+ allEntries.push(...entries);
2790
+ }
2791
+ }
2792
+ return allEntries;
2793
+ }
2794
+ Private.collectEntries = collectEntries;
2429
2795
  })(Private || (Private = {}));
2430
2796
  //# sourceMappingURL=listing.js.map