@jupyterlab/filebrowser 4.6.0-alpha.3 → 4.6.0-alpha.5
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/browser.d.ts +14 -0
- package/lib/browser.js +48 -1
- package/lib/browser.js.map +1 -1
- package/lib/crumbs.d.ts +76 -0
- package/lib/crumbs.js +362 -81
- package/lib/crumbs.js.map +1 -1
- package/lib/listing.d.ts +84 -11
- package/lib/listing.js +276 -99
- package/lib/listing.js.map +1 -1
- package/lib/model.d.ts +25 -0
- package/lib/model.js +48 -2
- package/lib/model.js.map +1 -1
- package/lib/opendialog.d.ts +7 -0
- package/lib/opendialog.js +5 -4
- package/lib/opendialog.js.map +1 -1
- package/lib/pathnavigator.d.ts +108 -0
- package/lib/pathnavigator.js +389 -0
- package/lib/pathnavigator.js.map +1 -0
- package/lib/tokens.d.ts +14 -0
- package/lib/tokens.js +1 -0
- package/lib/tokens.js.map +1 -1
- package/package.json +11 -11
- package/src/browser.ts +50 -1
- package/src/crumbs.ts +432 -87
- package/src/listing.ts +418 -119
- package/src/model.ts +63 -2
- package/src/opendialog.ts +13 -0
- package/src/pathnavigator.ts +456 -0
- package/src/tokens.ts +20 -0
- package/style/base.css +64 -35
- package/style/pathnavigator.css +72 -0
package/lib/listing.js
CHANGED
|
@@ -63,6 +63,10 @@ const ITEM_MODIFIED_CLASS = 'jp-DirListing-itemModified';
|
|
|
63
63
|
* The class name added to the listing item file size cell.
|
|
64
64
|
*/
|
|
65
65
|
const ITEM_FILE_SIZE_CLASS = 'jp-DirListing-itemFileSize';
|
|
66
|
+
/**
|
|
67
|
+
* The class name added to the listing item created cell.
|
|
68
|
+
*/
|
|
69
|
+
const ITEM_CREATED_CLASS = 'jp-DirListing-itemCreated';
|
|
66
70
|
/**
|
|
67
71
|
* The class name added to the label element that wraps each item's checkbox and
|
|
68
72
|
* the header's check-all checkbox.
|
|
@@ -84,6 +88,10 @@ const MODIFIED_ID_CLASS = 'jp-id-modified';
|
|
|
84
88
|
* The class name added to the file size column header cell.
|
|
85
89
|
*/
|
|
86
90
|
const FILE_SIZE_ID_CLASS = 'jp-id-filesize';
|
|
91
|
+
/**
|
|
92
|
+
* The class name added to the created column header cell.
|
|
93
|
+
*/
|
|
94
|
+
const CREATED_ID_CLASS = 'jp-id-created';
|
|
87
95
|
/**
|
|
88
96
|
* The mime type for a contents drag object.
|
|
89
97
|
*/
|
|
@@ -192,7 +200,8 @@ export class DirListing extends Widget {
|
|
|
192
200
|
name: null,
|
|
193
201
|
file_size: null,
|
|
194
202
|
is_selected: null,
|
|
195
|
-
last_modified: null
|
|
203
|
+
last_modified: null,
|
|
204
|
+
date_created: null
|
|
196
205
|
};
|
|
197
206
|
this._sortNotebooksFirst = false;
|
|
198
207
|
this._sortFileNamesNaturally = true;
|
|
@@ -200,6 +209,9 @@ export class DirListing extends Widget {
|
|
|
200
209
|
this._allowDragDropUpload = true;
|
|
201
210
|
// _focusIndex should never be set outside the range [0, this._items.length - 1]
|
|
202
211
|
this._focusIndex = 0;
|
|
212
|
+
this._focusPath = '';
|
|
213
|
+
this._modifiedStyle = 'short';
|
|
214
|
+
this._createdStyle = 'short';
|
|
203
215
|
this._allUploaded = new Signal(this);
|
|
204
216
|
this._width = null;
|
|
205
217
|
this._state = null;
|
|
@@ -228,9 +240,13 @@ export class DirListing extends Widget {
|
|
|
228
240
|
});
|
|
229
241
|
// Get the width of the "modified" column
|
|
230
242
|
this._updateModifiedSize(this.node);
|
|
243
|
+
// Get the width of the "created" column
|
|
244
|
+
this._updateCreatedSize(this.node);
|
|
231
245
|
const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
|
|
232
246
|
// hide the file size column by default
|
|
233
247
|
this._hiddenColumns.add('file_size');
|
|
248
|
+
// hide the date created column by default
|
|
249
|
+
this._hiddenColumns.add('date_created');
|
|
234
250
|
this._renderer.populateHeaderNode(headerNode, this.translator, this._hiddenColumns, this._columnSizes);
|
|
235
251
|
this._manager.activateRequested.connect(this._onActivateRequested, this);
|
|
236
252
|
}
|
|
@@ -319,6 +335,7 @@ export class DirListing extends Widget {
|
|
|
319
335
|
this._sortedItems = Private.sort(this.model.items(), state, this._sortNotebooksFirst, this._sortFileNamesNaturally, this.translator);
|
|
320
336
|
this._sortState = state;
|
|
321
337
|
this.update();
|
|
338
|
+
this._saveState();
|
|
322
339
|
}
|
|
323
340
|
/**
|
|
324
341
|
* Rename the first currently selected item.
|
|
@@ -491,18 +508,43 @@ export class DirListing extends Widget {
|
|
|
491
508
|
return;
|
|
492
509
|
}
|
|
493
510
|
const sizes = columns['sizes'];
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
511
|
+
const sortState = columns['sortState'];
|
|
512
|
+
// Set sort state before updating column sizes so that any
|
|
513
|
+
// state save triggered by _updateColumnSizes includes the
|
|
514
|
+
// correct sort state rather than the default.
|
|
515
|
+
if (sortState) {
|
|
516
|
+
this._sortState = sortState;
|
|
517
|
+
}
|
|
518
|
+
if (sizes) {
|
|
519
|
+
for (const [key, size] of Object.entries(sizes)) {
|
|
520
|
+
this._columnSizes[key] = size;
|
|
521
|
+
}
|
|
522
|
+
this._updateColumnSizes();
|
|
523
|
+
}
|
|
524
|
+
// Sort items and rebuild the header to reflect the restored
|
|
525
|
+
// sort state without calling sort() to avoid a redundant save.
|
|
526
|
+
if (sortState) {
|
|
527
|
+
this._sortedItems = Private.sort(this.model.items(), sortState, this._sortNotebooksFirst, this._sortFileNamesNaturally, this.translator);
|
|
528
|
+
this.headerNode.innerHTML = '';
|
|
529
|
+
this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns, this._columnSizes, sortState);
|
|
530
|
+
this.update();
|
|
499
531
|
}
|
|
500
|
-
this._updateColumnSizes();
|
|
501
532
|
}
|
|
502
533
|
catch (error) {
|
|
503
534
|
await state.remove(key);
|
|
504
535
|
}
|
|
505
536
|
}
|
|
537
|
+
/**
|
|
538
|
+
* Save the current column sizes and sort state to the state database.
|
|
539
|
+
*/
|
|
540
|
+
_saveState() {
|
|
541
|
+
if (this._state && this._stateColumnsKey) {
|
|
542
|
+
void this._state.save(this._stateColumnsKey, {
|
|
543
|
+
sizes: this._columnSizes,
|
|
544
|
+
sortState: { ...this._sortState }
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
506
548
|
/**
|
|
507
549
|
* Shut down kernels on the applicable currently selected items.
|
|
508
550
|
*
|
|
@@ -639,6 +681,30 @@ export class DirListing extends Widget {
|
|
|
639
681
|
this._selectionChanged.emit();
|
|
640
682
|
}
|
|
641
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Move focus to selected item, or to the first item if no item is selected.
|
|
686
|
+
*/
|
|
687
|
+
_focusContent() {
|
|
688
|
+
const items = this._sortedItems;
|
|
689
|
+
if (items.length === 0) {
|
|
690
|
+
this._focusItem(0);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
let index = this._focusPath !== ''
|
|
694
|
+
? ArrayExt.findFirstIndex(items, value => value.path === this._focusPath)
|
|
695
|
+
: -1;
|
|
696
|
+
// Fallbacks if path is missing/not found.
|
|
697
|
+
if (index === -1) {
|
|
698
|
+
index = Math.min(Math.max(this._focusIndex, 0), items.length - 1);
|
|
699
|
+
}
|
|
700
|
+
this._selectItem(index, false, true);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Handle `'activate-request'` messages.
|
|
704
|
+
*/
|
|
705
|
+
onActivateRequest(msg) {
|
|
706
|
+
this._focusContent();
|
|
707
|
+
}
|
|
642
708
|
/**
|
|
643
709
|
* Select an item by name.
|
|
644
710
|
*
|
|
@@ -826,32 +892,47 @@ export class DirListing extends Widget {
|
|
|
826
892
|
return width - this._paddingWidth * 2 - this._contentScrollbarWidth;
|
|
827
893
|
}
|
|
828
894
|
/**
|
|
829
|
-
* Update the modified column's size
|
|
895
|
+
* Update the 'modified' column's size
|
|
830
896
|
*/
|
|
831
897
|
_updateModifiedSize(node) {
|
|
898
|
+
this._modifiedStyle = this._computeDateColumnStyle(node, MODIFIED_ID_CLASS, 'last_modified');
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Update the 'date created' column's size
|
|
902
|
+
*/
|
|
903
|
+
_updateCreatedSize(node) {
|
|
904
|
+
this._createdStyle = this._computeDateColumnStyle(node, CREATED_ID_CLASS, 'date_created');
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Compute the Intl.RelativeTimeFormat style for a date column based on
|
|
908
|
+
* its pixel width: < 100px → 'narrow', 100–120px → 'short', > 120px → 'long'.
|
|
909
|
+
*/
|
|
910
|
+
_computeDateColumnStyle(node, columnClassName, columnName) {
|
|
832
911
|
var _a, _b;
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
836
|
-
(_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;
|
|
837
|
-
this._modifiedStyle =
|
|
838
|
-
this._modifiedWidth < 100
|
|
839
|
-
? 'narrow'
|
|
840
|
-
: this._modifiedWidth > 120
|
|
841
|
-
? 'long'
|
|
842
|
-
: 'short';
|
|
912
|
+
const columnElement = DOMUtils.findElement(node, columnClassName);
|
|
913
|
+
const width = (_b = (_a = this._columnSizes[columnName]) !== null && _a !== void 0 ? _a : columnElement === null || columnElement === void 0 ? void 0 : columnElement.getBoundingClientRect().width) !== null && _b !== void 0 ? _b : 83;
|
|
914
|
+
return width < 100 ? 'narrow' : width > 120 ? 'long' : 'short';
|
|
843
915
|
}
|
|
844
916
|
/**
|
|
845
917
|
* Rerender item nodes' modified dates, if the modified style has changed.
|
|
846
918
|
*/
|
|
847
919
|
_updateModifiedStyleAndSize() {
|
|
848
920
|
const oldModifiedStyle = this._modifiedStyle;
|
|
849
|
-
// Update both size and style
|
|
850
921
|
this._updateModifiedSize(this.node);
|
|
851
922
|
if (oldModifiedStyle !== this._modifiedStyle) {
|
|
852
923
|
this.updateModified(this._sortedItems, this._items);
|
|
853
924
|
}
|
|
854
925
|
}
|
|
926
|
+
/**
|
|
927
|
+
* Rerender item nodes' created dates, if the created style has changed.
|
|
928
|
+
*/
|
|
929
|
+
_updateCreatedStyleAndSize() {
|
|
930
|
+
const oldCreatedStyle = this._createdStyle;
|
|
931
|
+
this._updateCreatedSize(this.node);
|
|
932
|
+
if (oldCreatedStyle !== this._createdStyle) {
|
|
933
|
+
this.updateCreated(this._sortedItems, this._items);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
855
936
|
/**
|
|
856
937
|
* Update only the modified dates.
|
|
857
938
|
*/
|
|
@@ -860,6 +941,9 @@ export class DirListing extends Widget {
|
|
|
860
941
|
const node = nodes[i];
|
|
861
942
|
if (node && item.last_modified) {
|
|
862
943
|
const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
|
|
944
|
+
if (!modified) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
863
947
|
if (this.renderer.updateItemModified !== undefined) {
|
|
864
948
|
this.renderer.updateItemModified(modified, item.last_modified, this._modifiedStyle);
|
|
865
949
|
}
|
|
@@ -869,6 +953,26 @@ export class DirListing extends Widget {
|
|
|
869
953
|
}
|
|
870
954
|
});
|
|
871
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* Update only the created dates.
|
|
958
|
+
*/
|
|
959
|
+
updateCreated(items, nodes) {
|
|
960
|
+
items.forEach((item, i) => {
|
|
961
|
+
const node = nodes[i];
|
|
962
|
+
if (node && item.created) {
|
|
963
|
+
const created = DOMUtils.findElement(node, ITEM_CREATED_CLASS);
|
|
964
|
+
if (!created) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (this.renderer.updateItemCreated !== undefined) {
|
|
968
|
+
this.renderer.updateItemCreated(created, item.created, this._createdStyle);
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
DirListing.defaultRenderer.updateItemCreated(created, item.created, this._createdStyle);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
}
|
|
872
976
|
// Update item nodes based on widget state.
|
|
873
977
|
updateNodes(items, nodes, sizeOnly = false) {
|
|
874
978
|
var _a;
|
|
@@ -879,10 +983,10 @@ export class DirListing extends Widget {
|
|
|
879
983
|
// short-circuit in case if node is not yet ready
|
|
880
984
|
return;
|
|
881
985
|
}
|
|
882
|
-
return this.renderer.updateItemSize(node, item, this._modifiedStyle, this._columnSizes);
|
|
986
|
+
return this.renderer.updateItemSize(node, item, this._modifiedStyle, this._columnSizes, this._createdStyle);
|
|
883
987
|
}
|
|
884
988
|
const ft = this._manager.registry.getFileTypeForModel(item);
|
|
885
|
-
this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle, this._columnSizes);
|
|
989
|
+
this.renderer.updateItemNode(node, item, ft, this.translator, this._hiddenColumns, this.selection[item.path], this._modifiedStyle, this._columnSizes, this._createdStyle);
|
|
886
990
|
if (this.selection[item.path] &&
|
|
887
991
|
this._isCut &&
|
|
888
992
|
this._model.path === this._prevPath) {
|
|
@@ -1011,7 +1115,7 @@ export class DirListing extends Widget {
|
|
|
1011
1115
|
this._hiddenColumns.add(name);
|
|
1012
1116
|
}
|
|
1013
1117
|
this.headerNode.innerHTML = '';
|
|
1014
|
-
this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns, this._columnSizes);
|
|
1118
|
+
this._renderer.populateHeaderNode(this.headerNode, this.translator, this._hiddenColumns, this._columnSizes, this._sortState);
|
|
1015
1119
|
this._updateColumnSizes();
|
|
1016
1120
|
}
|
|
1017
1121
|
_updateColumnSizes(doNotGrowBeforeInclusive = null) {
|
|
@@ -1090,6 +1194,7 @@ export class DirListing extends Widget {
|
|
|
1090
1194
|
column.element.style.width = size === null ? '' : size + 'px';
|
|
1091
1195
|
}
|
|
1092
1196
|
this._updateModifiedStyleAndSize();
|
|
1197
|
+
this._updateCreatedStyleAndSize();
|
|
1093
1198
|
// Refresh sizes on the per item widths
|
|
1094
1199
|
if (this.isVisible) {
|
|
1095
1200
|
const items = this._items;
|
|
@@ -1097,11 +1202,7 @@ export class DirListing extends Widget {
|
|
|
1097
1202
|
this.updateNodes(this._sortedItems, this._items, true);
|
|
1098
1203
|
}
|
|
1099
1204
|
}
|
|
1100
|
-
|
|
1101
|
-
void this._state.save(this._stateColumnsKey, {
|
|
1102
|
-
sizes: this._columnSizes
|
|
1103
|
-
});
|
|
1104
|
-
}
|
|
1205
|
+
this._saveState();
|
|
1105
1206
|
}
|
|
1106
1207
|
get _visibleColumns() {
|
|
1107
1208
|
return DirListing.columns.filter(column => { var _a; return column.id === 'name' || !((_a = this._hiddenColumns) === null || _a === void 0 ? void 0 : _a.has(column.id)); });
|
|
@@ -1474,6 +1575,10 @@ export class DirListing extends Widget {
|
|
|
1474
1575
|
if (model.path === model.rootPath) {
|
|
1475
1576
|
return;
|
|
1476
1577
|
}
|
|
1578
|
+
// Check if we're at the navigation root boundary
|
|
1579
|
+
if (model.root && model.path === model.root) {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1477
1582
|
try {
|
|
1478
1583
|
await model.cd('..');
|
|
1479
1584
|
}
|
|
@@ -1489,9 +1594,8 @@ export class DirListing extends Widget {
|
|
|
1489
1594
|
if (this._inRename) {
|
|
1490
1595
|
return;
|
|
1491
1596
|
}
|
|
1492
|
-
switch (event.
|
|
1493
|
-
case
|
|
1494
|
-
// Enter
|
|
1597
|
+
switch (event.key) {
|
|
1598
|
+
case 'Enter': {
|
|
1495
1599
|
// Do nothing if any modifier keys are pressed.
|
|
1496
1600
|
if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
|
|
1497
1601
|
return;
|
|
@@ -1503,16 +1607,14 @@ export class DirListing extends Widget {
|
|
|
1503
1607
|
}
|
|
1504
1608
|
return;
|
|
1505
1609
|
}
|
|
1506
|
-
case
|
|
1507
|
-
// Up arrow
|
|
1610
|
+
case 'ArrowUp':
|
|
1508
1611
|
this._handleArrowY(event, -1);
|
|
1509
1612
|
return;
|
|
1510
|
-
case
|
|
1511
|
-
// Down arrow
|
|
1613
|
+
case 'ArrowDown':
|
|
1512
1614
|
this._handleArrowY(event, 1);
|
|
1513
1615
|
return;
|
|
1514
|
-
case
|
|
1515
|
-
|
|
1616
|
+
case ' ':
|
|
1617
|
+
case 'Spacebar': {
|
|
1516
1618
|
if (event.ctrlKey) {
|
|
1517
1619
|
// Follow the Windows and Ubuntu convention: you must press `ctrl` +
|
|
1518
1620
|
// `space` in order to toggle whether an item is selected.
|
|
@@ -1555,7 +1657,7 @@ export class DirListing extends Widget {
|
|
|
1555
1657
|
event.key.length === 1 &&
|
|
1556
1658
|
// Don't gobble up the space key on the check-all checkbox (which the
|
|
1557
1659
|
// browser treats as a click event).
|
|
1558
|
-
!((event.key === ' ' || event.
|
|
1660
|
+
!((event.key === ' ' || event.key === 'Spacebar') &&
|
|
1559
1661
|
event.target.type === 'checkbox')) {
|
|
1560
1662
|
if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
|
|
1561
1663
|
return;
|
|
@@ -1908,16 +2010,20 @@ export class DirListing extends Widget {
|
|
|
1908
2010
|
* @param index The index of the item node to focus
|
|
1909
2011
|
*/
|
|
1910
2012
|
_focusItem(index) {
|
|
2013
|
+
var _a, _b;
|
|
1911
2014
|
const items = this._items;
|
|
1912
2015
|
if (items.length === 0) {
|
|
1913
2016
|
// Focus the top node if the folder is empty and therefore there are no
|
|
1914
2017
|
// items inside the folder to focus.
|
|
1915
2018
|
this._focusIndex = 0;
|
|
2019
|
+
this._focusPath = '';
|
|
1916
2020
|
this.node.focus();
|
|
1917
2021
|
return;
|
|
1918
2022
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
2023
|
+
const clampedIndex = Math.min(Math.max(index, 0), items.length - 1);
|
|
2024
|
+
this._focusIndex = clampedIndex;
|
|
2025
|
+
this._focusPath = (_b = (_a = this._sortedItems[clampedIndex]) === null || _a === void 0 ? void 0 : _a.path) !== null && _b !== void 0 ? _b : '';
|
|
2026
|
+
const node = items[clampedIndex];
|
|
1921
2027
|
const nameNode = this.renderer.getNameNode(node);
|
|
1922
2028
|
if (nameNode) {
|
|
1923
2029
|
// Make the filename text node focusable so that it receives keyboard
|
|
@@ -2260,6 +2366,16 @@ export class DirListing extends Widget {
|
|
|
2260
2366
|
caretSide: 'left',
|
|
2261
2367
|
grow: 1
|
|
2262
2368
|
},
|
|
2369
|
+
{
|
|
2370
|
+
id: 'date_created',
|
|
2371
|
+
className: CREATED_ID_CLASS,
|
|
2372
|
+
itemClassName: ITEM_CREATED_CLASS,
|
|
2373
|
+
minWidth: 60,
|
|
2374
|
+
resizable: true,
|
|
2375
|
+
sortable: true,
|
|
2376
|
+
caretSide: 'left',
|
|
2377
|
+
grow: 1
|
|
2378
|
+
},
|
|
2263
2379
|
{
|
|
2264
2380
|
id: 'file_size',
|
|
2265
2381
|
className: FILE_SIZE_ID_CLASS,
|
|
@@ -2301,12 +2417,21 @@ export class DirListing extends Widget {
|
|
|
2301
2417
|
fileSize.className = ITEM_FILE_SIZE_CLASS;
|
|
2302
2418
|
return fileSize;
|
|
2303
2419
|
},
|
|
2420
|
+
date_created: () => {
|
|
2421
|
+
const created = document.createElement('span');
|
|
2422
|
+
created.className = ITEM_CREATED_CLASS;
|
|
2423
|
+
return created;
|
|
2424
|
+
},
|
|
2304
2425
|
is_selected: () => this.createCheckboxWrapperNode()
|
|
2305
2426
|
};
|
|
2306
2427
|
/**
|
|
2307
2428
|
* Register of most recent arguments for last modified column update.
|
|
2308
2429
|
*/
|
|
2309
2430
|
this._modifiedColumnLastUpdate = new WeakMap();
|
|
2431
|
+
/**
|
|
2432
|
+
* Register of most recent arguments for date created column update.
|
|
2433
|
+
*/
|
|
2434
|
+
this._createdColumnLastUpdate = new WeakMap();
|
|
2310
2435
|
this._lastRenderedState = new WeakMap();
|
|
2311
2436
|
}
|
|
2312
2437
|
/**
|
|
@@ -2331,7 +2456,7 @@ export class DirListing extends Widget {
|
|
|
2331
2456
|
*
|
|
2332
2457
|
* @param node - The header node to populate.
|
|
2333
2458
|
*/
|
|
2334
|
-
populateHeaderNode(node, translator, hiddenColumns, columnsSizes) {
|
|
2459
|
+
populateHeaderNode(node, translator, hiddenColumns, columnsSizes, sortState) {
|
|
2335
2460
|
translator = translator || nullTranslator;
|
|
2336
2461
|
const trans = translator.load('jupyterlab');
|
|
2337
2462
|
const elementCreators = {
|
|
@@ -2344,6 +2469,10 @@ export class DirListing extends Widget {
|
|
|
2344
2469
|
small: trans.__('Size'),
|
|
2345
2470
|
large: trans.__('File Size')
|
|
2346
2471
|
}),
|
|
2472
|
+
date_created: () => this._createHeaderItemNodeWithSizes({
|
|
2473
|
+
small: trans.__('Created'),
|
|
2474
|
+
large: trans.__('Date Created')
|
|
2475
|
+
}),
|
|
2347
2476
|
is_selected: () => this.createCheckboxWrapperNode({
|
|
2348
2477
|
alwaysVisible: true,
|
|
2349
2478
|
headerNode: true
|
|
@@ -2369,10 +2498,7 @@ export class DirListing extends Widget {
|
|
|
2369
2498
|
node.appendChild(resizer);
|
|
2370
2499
|
}
|
|
2371
2500
|
}
|
|
2372
|
-
|
|
2373
|
-
name.classList.add(SELECTED_CLASS);
|
|
2374
|
-
// set the initial caret icon
|
|
2375
|
-
Private.updateCaret(DOMUtils.findElement(name, HEADER_ITEM_ICON_CLASS), 'right', 'up');
|
|
2501
|
+
this._updateSortIndicator(node, sortState !== null && sortState !== void 0 ? sortState : { direction: 'ascending', key: 'name' });
|
|
2376
2502
|
}
|
|
2377
2503
|
/**
|
|
2378
2504
|
* Handle a header click.
|
|
@@ -2384,52 +2510,52 @@ export class DirListing extends Widget {
|
|
|
2384
2510
|
* @returns The sort state of the header after the click event.
|
|
2385
2511
|
*/
|
|
2386
2512
|
handleHeaderClick(node, event) {
|
|
2387
|
-
const state = { direction: 'ascending', key: 'name' };
|
|
2388
2513
|
const target = event.target;
|
|
2389
2514
|
const sortableColumns = DirListing.columns.filter(Private.isSortable);
|
|
2390
2515
|
for (const column of sortableColumns) {
|
|
2391
2516
|
const header = node.querySelector(`.${column.className}`);
|
|
2392
2517
|
if (!header) {
|
|
2393
|
-
// skip if the column is hidden
|
|
2394
2518
|
continue;
|
|
2395
2519
|
}
|
|
2396
2520
|
if (header.contains(target)) {
|
|
2397
|
-
|
|
2398
|
-
const
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2521
|
+
const isSelected = header.classList.contains(SELECTED_CLASS);
|
|
2522
|
+
const isDescending = header.classList.contains(DESCENDING_CLASS);
|
|
2523
|
+
let direction = 'ascending';
|
|
2524
|
+
if (isSelected && !isDescending) {
|
|
2525
|
+
direction = 'descending';
|
|
2526
|
+
}
|
|
2527
|
+
const state = { direction, key: column.id };
|
|
2528
|
+
this._updateSortIndicator(node, state);
|
|
2529
|
+
return state;
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
return { direction: 'ascending', key: 'name' };
|
|
2533
|
+
}
|
|
2534
|
+
_updateSortIndicator(node, sortState) {
|
|
2535
|
+
const sortableColumns = DirListing.columns.filter(Private.isSortable);
|
|
2536
|
+
for (const column of sortableColumns) {
|
|
2537
|
+
const header = node.querySelector(`.${column.className}`);
|
|
2538
|
+
if (!header) {
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
const headerIcon = DOMUtils.findElement(header, HEADER_ITEM_ICON_CLASS);
|
|
2542
|
+
if (column.id === sortState.key) {
|
|
2543
|
+
header.classList.add(SELECTED_CLASS);
|
|
2544
|
+
if (sortState.direction === 'descending') {
|
|
2545
|
+
header.classList.add(DESCENDING_CLASS);
|
|
2546
|
+
Private.updateCaret(headerIcon, column.caretSide, 'down');
|
|
2409
2547
|
}
|
|
2410
2548
|
else {
|
|
2411
2549
|
header.classList.remove(DESCENDING_CLASS);
|
|
2412
2550
|
Private.updateCaret(headerIcon, column.caretSide, 'up');
|
|
2413
2551
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
const otherHeader = node.querySelector(`.${otherColumn.className}`);
|
|
2420
|
-
if (!otherHeader) {
|
|
2421
|
-
// skip if hidden
|
|
2422
|
-
continue;
|
|
2423
|
-
}
|
|
2424
|
-
otherHeader.classList.remove(SELECTED_CLASS);
|
|
2425
|
-
otherHeader.classList.remove(DESCENDING_CLASS);
|
|
2426
|
-
const otherHeaderIcon = DOMUtils.findElement(otherHeader, HEADER_ITEM_ICON_CLASS);
|
|
2427
|
-
Private.updateCaret(otherHeaderIcon, otherColumn.caretSide);
|
|
2428
|
-
}
|
|
2429
|
-
return state;
|
|
2552
|
+
}
|
|
2553
|
+
else {
|
|
2554
|
+
header.classList.remove(SELECTED_CLASS);
|
|
2555
|
+
header.classList.remove(DESCENDING_CLASS);
|
|
2556
|
+
Private.updateCaret(headerIcon, column.caretSide);
|
|
2430
2557
|
}
|
|
2431
2558
|
}
|
|
2432
|
-
return state;
|
|
2433
2559
|
}
|
|
2434
2560
|
/**
|
|
2435
2561
|
* Create a new item node for a dir listing.
|
|
@@ -2502,25 +2628,47 @@ export class DirListing extends Widget {
|
|
|
2502
2628
|
* @param modifiedStyle - The date style for the modified column: narrow, short, or long
|
|
2503
2629
|
*/
|
|
2504
2630
|
updateItemModified(modified, modifiedDate, modifiedStyle) {
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2631
|
+
this._updateItemDate(modified, modifiedDate, modifiedStyle, this._modifiedColumnLastUpdate);
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Update an item's created date.
|
|
2635
|
+
*
|
|
2636
|
+
* @param created - Element containing the file's created date.
|
|
2637
|
+
*
|
|
2638
|
+
* @param createdDate - String representation of the created date.
|
|
2639
|
+
*
|
|
2640
|
+
* @param createdStyle - The date style for the created column: narrow, short, or long
|
|
2641
|
+
*/
|
|
2642
|
+
updateItemCreated(created, createdDate, createdStyle) {
|
|
2643
|
+
this._updateItemDate(created, createdDate, createdStyle, this._createdColumnLastUpdate);
|
|
2644
|
+
}
|
|
2645
|
+
/**
|
|
2646
|
+
* Shared implementation for updating a date column element.
|
|
2647
|
+
*
|
|
2648
|
+
* Formatting dates is expensive (0.1-0.2ms per call, so over 150 files
|
|
2649
|
+
* can easily already choke the renderer), so we do the bare minimum check
|
|
2650
|
+
* of comparing if an update is needed using a last update cache.
|
|
2651
|
+
*
|
|
2652
|
+
* @param element - The date column element to update.
|
|
2653
|
+
*
|
|
2654
|
+
* @param dateString - String representation of the date.
|
|
2655
|
+
*
|
|
2656
|
+
* @param style - The date style: narrow, short, or long.
|
|
2657
|
+
*
|
|
2658
|
+
* @param cache - The WeakMap cache tracking the last rendered state for
|
|
2659
|
+
* this column.
|
|
2660
|
+
*/
|
|
2661
|
+
_updateItemDate(element, dateString, style, cache) {
|
|
2662
|
+
const previousUpdate = cache.get(element);
|
|
2663
|
+
if ((previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.date) === dateString &&
|
|
2664
|
+
(previousUpdate === null || previousUpdate === void 0 ? void 0 : previousUpdate.style) === style) {
|
|
2512
2665
|
return;
|
|
2513
2666
|
}
|
|
2514
|
-
const parsedDate = new Date(
|
|
2667
|
+
const parsedDate = new Date(dateString);
|
|
2515
2668
|
// Render the date in one of multiple formats, depending on the container's size
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
modified.title = modTitle;
|
|
2520
|
-
this._modifiedColumnLastUpdate.set(modified, {
|
|
2521
|
-
date: modifiedDate,
|
|
2522
|
-
style: modifiedStyle
|
|
2523
|
-
});
|
|
2669
|
+
element.textContent = Time.formatHuman(parsedDate, style);
|
|
2670
|
+
element.title = Time.format(parsedDate);
|
|
2671
|
+
cache.set(element, { date: dateString, style });
|
|
2524
2672
|
}
|
|
2525
2673
|
/**
|
|
2526
2674
|
* Update an item node to reflect the current state of a model.
|
|
@@ -2532,7 +2680,8 @@ export class DirListing extends Widget {
|
|
|
2532
2680
|
* @param fileType - The file type of the item, if applicable.
|
|
2533
2681
|
*
|
|
2534
2682
|
*/
|
|
2535
|
-
updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle, columnsSizes) {
|
|
2683
|
+
updateItemNode(node, model, fileType, translator, hiddenColumns, selected, modifiedStyle, columnsSizes, createdStyle) {
|
|
2684
|
+
var _a;
|
|
2536
2685
|
if (selected) {
|
|
2537
2686
|
node.classList.add(SELECTED_CLASS);
|
|
2538
2687
|
}
|
|
@@ -2546,8 +2695,10 @@ export class DirListing extends Widget {
|
|
|
2546
2695
|
name: model.name,
|
|
2547
2696
|
selected,
|
|
2548
2697
|
lastModified: model.last_modified,
|
|
2698
|
+
created: model.created,
|
|
2549
2699
|
modifiedStyle,
|
|
2550
|
-
|
|
2700
|
+
createdStyle,
|
|
2701
|
+
hiddenColumns: Array.from(hiddenColumns !== null && hiddenColumns !== void 0 ? hiddenColumns : []).sort(),
|
|
2551
2702
|
columnsSizes,
|
|
2552
2703
|
fileSize: model.size
|
|
2553
2704
|
});
|
|
@@ -2563,6 +2714,7 @@ export class DirListing extends Widget {
|
|
|
2563
2714
|
const nameColumn = DOMUtils.findElement(node, ITEM_NAME_COLUMN_CLASS);
|
|
2564
2715
|
let modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
|
|
2565
2716
|
let fileSize = DOMUtils.findElement(node, ITEM_FILE_SIZE_CLASS);
|
|
2717
|
+
let created = DOMUtils.findElement(node, ITEM_CREATED_CLASS);
|
|
2566
2718
|
const showFileCheckboxes = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('is_selected'));
|
|
2567
2719
|
if (checkboxWrapper && !showFileCheckboxes) {
|
|
2568
2720
|
node.removeChild(checkboxWrapper);
|
|
@@ -2574,18 +2726,31 @@ export class DirListing extends Widget {
|
|
|
2574
2726
|
const showModified = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('last_modified'));
|
|
2575
2727
|
if (modified && !showModified) {
|
|
2576
2728
|
node.removeChild(modified);
|
|
2729
|
+
modified = undefined;
|
|
2577
2730
|
}
|
|
2578
2731
|
else if (showModified && !modified) {
|
|
2579
2732
|
modified = this.itemFactories.last_modified();
|
|
2580
2733
|
nameColumn.insertAdjacentElement('afterend', modified);
|
|
2581
2734
|
}
|
|
2735
|
+
const showCreated = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('date_created'));
|
|
2736
|
+
if (created && !showCreated) {
|
|
2737
|
+
node.removeChild(created);
|
|
2738
|
+
created = undefined;
|
|
2739
|
+
}
|
|
2740
|
+
else if (showCreated && !created) {
|
|
2741
|
+
created = this.itemFactories.date_created();
|
|
2742
|
+
(modified !== null && modified !== void 0 ? modified : nameColumn).insertAdjacentElement('afterend', created);
|
|
2743
|
+
}
|
|
2582
2744
|
const showFileSize = !(hiddenColumns === null || hiddenColumns === void 0 ? void 0 : hiddenColumns.has('file_size'));
|
|
2583
2745
|
if (fileSize && !showFileSize) {
|
|
2584
2746
|
node.removeChild(fileSize);
|
|
2585
2747
|
}
|
|
2586
2748
|
else if (showFileSize && !fileSize) {
|
|
2587
2749
|
fileSize = this.itemFactories.file_size();
|
|
2588
|
-
(
|
|
2750
|
+
const insertAfter = (_a = created !== null && created !== void 0 ? created : modified) !== null && _a !== void 0 ? _a : nameColumn;
|
|
2751
|
+
if (insertAfter) {
|
|
2752
|
+
insertAfter.insertAdjacentElement('afterend', fileSize);
|
|
2753
|
+
}
|
|
2589
2754
|
}
|
|
2590
2755
|
// render the file item's icon
|
|
2591
2756
|
requestAnimationFrame(() => {
|
|
@@ -2655,12 +2820,12 @@ export class DirListing extends Widget {
|
|
|
2655
2820
|
checkbox.setAttribute('aria-label', ariaLabel);
|
|
2656
2821
|
checkbox.checked = selected !== null && selected !== void 0 ? selected : false;
|
|
2657
2822
|
}
|
|
2658
|
-
this.updateItemSize(node, model, modifiedStyle, columnsSizes);
|
|
2823
|
+
this.updateItemSize(node, model, modifiedStyle, columnsSizes, createdStyle);
|
|
2659
2824
|
}
|
|
2660
2825
|
/**
|
|
2661
2826
|
* Update size of item nodes, assuming that model has not changed.
|
|
2662
2827
|
*/
|
|
2663
|
-
updateItemSize(node, model, modifiedStyle, columnsSizes) {
|
|
2828
|
+
updateItemSize(node, model, modifiedStyle, columnsSizes, createdStyle) {
|
|
2664
2829
|
if (columnsSizes) {
|
|
2665
2830
|
for (const column of DirListing.columns) {
|
|
2666
2831
|
const element = DOMUtils.findElement(node, column.itemClassName);
|
|
@@ -2678,6 +2843,10 @@ export class DirListing extends Widget {
|
|
|
2678
2843
|
if (model.last_modified && modified) {
|
|
2679
2844
|
this.updateItemModified(modified, model.last_modified, modifiedStyle !== null && modifiedStyle !== void 0 ? modifiedStyle : 'short');
|
|
2680
2845
|
}
|
|
2846
|
+
let created = DOMUtils.findElement(node, ITEM_CREATED_CLASS);
|
|
2847
|
+
if (model.created && created) {
|
|
2848
|
+
this.updateItemCreated(created, model.created, createdStyle !== null && createdStyle !== void 0 ? createdStyle : 'short');
|
|
2849
|
+
}
|
|
2681
2850
|
}
|
|
2682
2851
|
/**
|
|
2683
2852
|
* Get the node containing the file name.
|
|
@@ -2805,13 +2974,13 @@ var Private;
|
|
|
2805
2974
|
resolve(edit.value);
|
|
2806
2975
|
};
|
|
2807
2976
|
edit.onkeydown = (event) => {
|
|
2808
|
-
switch (event.
|
|
2809
|
-
case
|
|
2977
|
+
switch (event.key) {
|
|
2978
|
+
case 'Enter':
|
|
2810
2979
|
event.stopPropagation();
|
|
2811
2980
|
event.preventDefault();
|
|
2812
2981
|
edit.blur();
|
|
2813
2982
|
break;
|
|
2814
|
-
case
|
|
2983
|
+
case 'Escape':
|
|
2815
2984
|
event.stopPropagation();
|
|
2816
2985
|
event.preventDefault();
|
|
2817
2986
|
edit.value = original;
|
|
@@ -2906,6 +3075,14 @@ var Private;
|
|
|
2906
3075
|
return ((_a = b.size) !== null && _a !== void 0 ? _a : 0) - ((_b = a.size) !== null && _b !== void 0 ? _b : 0);
|
|
2907
3076
|
}));
|
|
2908
3077
|
}
|
|
3078
|
+
else if (state.key === 'date_created') {
|
|
3079
|
+
// Sort by date created
|
|
3080
|
+
copy.sort(compare((a, b) => {
|
|
3081
|
+
var _a, _b;
|
|
3082
|
+
return (new Date((_a = a.created) !== null && _a !== void 0 ? _a : '').getTime() -
|
|
3083
|
+
new Date((_b = b.created) !== null && _b !== void 0 ? _b : '').getTime());
|
|
3084
|
+
}));
|
|
3085
|
+
}
|
|
2909
3086
|
else {
|
|
2910
3087
|
// Sort by name
|
|
2911
3088
|
copy.sort(compare((a, b) => {
|