@jupyterlab/filebrowser 4.2.0-beta.1 → 4.2.0-beta.3
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 +2 -2
- package/lib/browser.js +2 -2
- package/lib/crumbs.d.ts +1 -1
- package/lib/crumbs.js +1 -1
- package/lib/listing.d.ts +31 -3
- package/lib/listing.js +100 -48
- package/lib/listing.js.map +1 -1
- package/lib/model.d.ts +2 -2
- package/lib/model.js +13 -15
- package/lib/model.js.map +1 -1
- package/lib/uploadstatus.js +1 -1
- package/package.json +11 -13
- package/src/browser.ts +2 -2
- package/src/crumbs.ts +1 -1
- package/src/listing.ts +161 -64
- package/src/model.ts +13 -20
- package/src/uploadstatus.tsx +1 -1
- package/style/base.css +8 -4
package/src/listing.ts
CHANGED
|
@@ -209,7 +209,7 @@ export class DirListing extends Widget {
|
|
|
209
209
|
/**
|
|
210
210
|
* Construct a new file browser directory listing widget.
|
|
211
211
|
*
|
|
212
|
-
* @param
|
|
212
|
+
* @param options The constructor options
|
|
213
213
|
*/
|
|
214
214
|
constructor(options: DirListing.IOptions) {
|
|
215
215
|
super({
|
|
@@ -227,6 +227,9 @@ export class DirListing extends Widget {
|
|
|
227
227
|
this._manager = this._model.manager;
|
|
228
228
|
this._renderer = options.renderer || DirListing.defaultRenderer;
|
|
229
229
|
|
|
230
|
+
// Get the width of the "modified" column
|
|
231
|
+
this._updateModifiedSize(this.node);
|
|
232
|
+
|
|
230
233
|
const headerNode = DOMUtils.findElement(this.node, HEADER_CLASS);
|
|
231
234
|
// hide the file size column by default
|
|
232
235
|
this._hiddenColumns.add('file_size');
|
|
@@ -806,6 +809,99 @@ export class DirListing extends Widget {
|
|
|
806
809
|
}
|
|
807
810
|
}
|
|
808
811
|
|
|
812
|
+
// Update the modified column's size
|
|
813
|
+
private _updateModifiedSize(node: HTMLElement) {
|
|
814
|
+
const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
|
|
815
|
+
this._modifiedWidth = modified?.getBoundingClientRect().width ?? 83;
|
|
816
|
+
this._modifiedStyle =
|
|
817
|
+
this._modifiedWidth < 90
|
|
818
|
+
? 'narrow'
|
|
819
|
+
: this._modifiedWidth > 110
|
|
820
|
+
? 'long'
|
|
821
|
+
: 'short';
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Update only the modified dates.
|
|
825
|
+
protected updateModified(items: Contents.IModel[], nodes: HTMLElement[]) {
|
|
826
|
+
items.forEach((item, i) => {
|
|
827
|
+
const node = nodes[i];
|
|
828
|
+
if (item.last_modified) {
|
|
829
|
+
const modified = DOMUtils.findElement(node, ITEM_MODIFIED_CLASS);
|
|
830
|
+
if (this.renderer.updateItemModified !== undefined) {
|
|
831
|
+
this.renderer.updateItemModified(
|
|
832
|
+
modified,
|
|
833
|
+
item.last_modified,
|
|
834
|
+
this._modifiedStyle
|
|
835
|
+
);
|
|
836
|
+
} else {
|
|
837
|
+
DirListing.defaultRenderer.updateItemModified(
|
|
838
|
+
modified,
|
|
839
|
+
item.last_modified,
|
|
840
|
+
this._modifiedStyle
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Update item nodes based on widget state.
|
|
848
|
+
protected updateNodes(items: Contents.IModel[], nodes: HTMLElement[]) {
|
|
849
|
+
items.forEach((item, i) => {
|
|
850
|
+
const node = nodes[i];
|
|
851
|
+
const ft = this._manager.registry.getFileTypeForModel(item);
|
|
852
|
+
this.renderer.updateItemNode(
|
|
853
|
+
node,
|
|
854
|
+
item,
|
|
855
|
+
ft,
|
|
856
|
+
this.translator,
|
|
857
|
+
this._hiddenColumns,
|
|
858
|
+
this.selection[item.path],
|
|
859
|
+
this._modifiedStyle
|
|
860
|
+
);
|
|
861
|
+
if (
|
|
862
|
+
this.selection[item.path] &&
|
|
863
|
+
this._isCut &&
|
|
864
|
+
this._model.path === this._prevPath
|
|
865
|
+
) {
|
|
866
|
+
node.classList.add(CUT_CLASS);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// add metadata to the node
|
|
870
|
+
node.setAttribute(
|
|
871
|
+
'data-isdir',
|
|
872
|
+
item.type === 'directory' ? 'true' : 'false'
|
|
873
|
+
);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Handle the selectors on the widget node.
|
|
877
|
+
const selected = Object.keys(this.selection).length;
|
|
878
|
+
if (selected) {
|
|
879
|
+
this.addClass(SELECTED_CLASS);
|
|
880
|
+
if (selected > 1) {
|
|
881
|
+
this.addClass(MULTI_SELECTED_CLASS);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// Handle file session statuses.
|
|
886
|
+
const paths = items.map(item => item.path);
|
|
887
|
+
for (const session of this._model.sessions()) {
|
|
888
|
+
const index = ArrayExt.firstIndexOf(paths, session.path);
|
|
889
|
+
const node = nodes[index];
|
|
890
|
+
// Node may have been filtered out.
|
|
891
|
+
if (node) {
|
|
892
|
+
let name = session.kernel?.name;
|
|
893
|
+
const specs = this._model.specs;
|
|
894
|
+
|
|
895
|
+
node.classList.add(RUNNING_CLASS);
|
|
896
|
+
if (specs && name) {
|
|
897
|
+
const spec = specs.kernelspecs[name];
|
|
898
|
+
name = spec ? spec.display_name : this._trans.__('unknown');
|
|
899
|
+
}
|
|
900
|
+
node.title = this._trans.__('%1\nKernel: %2', node.title, name);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
809
905
|
/**
|
|
810
906
|
* A handler invoked on an `'update-request'` message.
|
|
811
907
|
*/
|
|
@@ -879,60 +975,7 @@ export class DirListing extends Widget {
|
|
|
879
975
|
);
|
|
880
976
|
}
|
|
881
977
|
|
|
882
|
-
|
|
883
|
-
items.forEach((item, i) => {
|
|
884
|
-
const node = nodes[i];
|
|
885
|
-
const ft = this._manager.registry.getFileTypeForModel(item);
|
|
886
|
-
renderer.updateItemNode(
|
|
887
|
-
node,
|
|
888
|
-
item,
|
|
889
|
-
ft,
|
|
890
|
-
this.translator,
|
|
891
|
-
this._hiddenColumns,
|
|
892
|
-
this.selection[item.path]
|
|
893
|
-
);
|
|
894
|
-
if (
|
|
895
|
-
this.selection[item.path] &&
|
|
896
|
-
this._isCut &&
|
|
897
|
-
this._model.path === this._prevPath
|
|
898
|
-
) {
|
|
899
|
-
node.classList.add(CUT_CLASS);
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// add metadata to the node
|
|
903
|
-
node.setAttribute(
|
|
904
|
-
'data-isdir',
|
|
905
|
-
item.type === 'directory' ? 'true' : 'false'
|
|
906
|
-
);
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
// Handle the selectors on the widget node.
|
|
910
|
-
const selected = Object.keys(this.selection).length;
|
|
911
|
-
if (selected) {
|
|
912
|
-
this.addClass(SELECTED_CLASS);
|
|
913
|
-
if (selected > 1) {
|
|
914
|
-
this.addClass(MULTI_SELECTED_CLASS);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
// Handle file session statuses.
|
|
919
|
-
const paths = items.map(item => item.path);
|
|
920
|
-
for (const session of this._model.sessions()) {
|
|
921
|
-
const index = ArrayExt.firstIndexOf(paths, session.path);
|
|
922
|
-
const node = nodes[index];
|
|
923
|
-
// Node may have been filtered out.
|
|
924
|
-
if (node) {
|
|
925
|
-
let name = session.kernel?.name;
|
|
926
|
-
const specs = this._model.specs;
|
|
927
|
-
|
|
928
|
-
node.classList.add(RUNNING_CLASS);
|
|
929
|
-
if (specs && name) {
|
|
930
|
-
const spec = specs.kernelspecs[name];
|
|
931
|
-
name = spec ? spec.display_name : this._trans.__('unknown');
|
|
932
|
-
}
|
|
933
|
-
node.title = this._trans.__('%1\nKernel: %2', node.title, name);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
978
|
+
this.updateNodes(items, nodes);
|
|
936
979
|
|
|
937
980
|
this._prevPath = this._model.path;
|
|
938
981
|
}
|
|
@@ -941,6 +984,13 @@ export class DirListing extends Widget {
|
|
|
941
984
|
const { width } =
|
|
942
985
|
msg.width === -1 ? this.node.getBoundingClientRect() : msg;
|
|
943
986
|
this.toggleClass('jp-DirListing-narrow', width < 250);
|
|
987
|
+
|
|
988
|
+
// Rerender item nodes' modified dates, if the modified style has changed.
|
|
989
|
+
const oldModifiedStyle = this._modifiedStyle;
|
|
990
|
+
this._updateModifiedSize(this.node);
|
|
991
|
+
if (oldModifiedStyle !== this._modifiedStyle) {
|
|
992
|
+
this.updateModified(this._sortedItems, this._items);
|
|
993
|
+
}
|
|
944
994
|
}
|
|
945
995
|
|
|
946
996
|
setColumnVisibility(
|
|
@@ -2094,6 +2144,9 @@ export class DirListing extends Widget {
|
|
|
2094
2144
|
private _sortNotebooksFirst = false;
|
|
2095
2145
|
// _focusIndex should never be set outside the range [0, this._items.length - 1]
|
|
2096
2146
|
private _focusIndex = 0;
|
|
2147
|
+
// Width of the "last modified" column for an individual file
|
|
2148
|
+
private _modifiedWidth: number;
|
|
2149
|
+
private _modifiedStyle: Time.HumanStyle;
|
|
2097
2150
|
}
|
|
2098
2151
|
|
|
2099
2152
|
/**
|
|
@@ -2201,6 +2254,21 @@ export namespace DirListing {
|
|
|
2201
2254
|
hiddenColumns?: Set<DirListing.ToggleableColumn>
|
|
2202
2255
|
): HTMLElement;
|
|
2203
2256
|
|
|
2257
|
+
/**
|
|
2258
|
+
* Update an item's last modified date.
|
|
2259
|
+
*
|
|
2260
|
+
* @param modified - Element containing the file's last modified date.
|
|
2261
|
+
*
|
|
2262
|
+
* @param modifiedDate - String representation of the last modified date.
|
|
2263
|
+
*
|
|
2264
|
+
* @param modifiedStyle - The date style for the modified column: narrow, short, or long
|
|
2265
|
+
*/
|
|
2266
|
+
updateItemModified?(
|
|
2267
|
+
modified: HTMLElement,
|
|
2268
|
+
modifiedDate: string,
|
|
2269
|
+
modifiedStyle: Time.HumanStyle
|
|
2270
|
+
): void;
|
|
2271
|
+
|
|
2204
2272
|
/**
|
|
2205
2273
|
* Update an item node to reflect the current state of a model.
|
|
2206
2274
|
*
|
|
@@ -2208,6 +2276,8 @@ export namespace DirListing {
|
|
|
2208
2276
|
*
|
|
2209
2277
|
* @param model - The model object to use for the item state.
|
|
2210
2278
|
*
|
|
2279
|
+
* @param modifiedStyle - The date style for the modified column: narrow, short, or long
|
|
2280
|
+
*
|
|
2211
2281
|
* @param fileType - The file type of the item, if applicable.
|
|
2212
2282
|
*/
|
|
2213
2283
|
updateItemNode(
|
|
@@ -2216,7 +2286,8 @@ export namespace DirListing {
|
|
|
2216
2286
|
fileType?: DocumentRegistry.IFileType,
|
|
2217
2287
|
translator?: ITranslator,
|
|
2218
2288
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
2219
|
-
selected?: boolean
|
|
2289
|
+
selected?: boolean,
|
|
2290
|
+
modifiedStyle?: Time.HumanStyle
|
|
2220
2291
|
): void;
|
|
2221
2292
|
|
|
2222
2293
|
/**
|
|
@@ -2297,7 +2368,7 @@ export namespace DirListing {
|
|
|
2297
2368
|
const trans = translator.load('jupyterlab');
|
|
2298
2369
|
const name = this.createHeaderItemNode(trans.__('Name'));
|
|
2299
2370
|
const narrow = document.createElement('div');
|
|
2300
|
-
const modified = this.createHeaderItemNode(trans.__('
|
|
2371
|
+
const modified = this.createHeaderItemNode(trans.__('Modified'));
|
|
2301
2372
|
const fileSize = this.createHeaderItemNode(trans.__('File Size'));
|
|
2302
2373
|
name.classList.add(NAME_ID_CLASS);
|
|
2303
2374
|
name.classList.add(SELECTED_CLASS);
|
|
@@ -2524,6 +2595,32 @@ export namespace DirListing {
|
|
|
2524
2595
|
return labelWrapper;
|
|
2525
2596
|
}
|
|
2526
2597
|
|
|
2598
|
+
/**
|
|
2599
|
+
* Update an item's last modified date.
|
|
2600
|
+
*
|
|
2601
|
+
* @param modified - Element containing the file's last modified date.
|
|
2602
|
+
*
|
|
2603
|
+
* @param modifiedDate - String representation of the last modified date.
|
|
2604
|
+
*
|
|
2605
|
+
* @param modifiedStyle - The date style for the modified column: narrow, short, or long
|
|
2606
|
+
*/
|
|
2607
|
+
updateItemModified(
|
|
2608
|
+
modified: HTMLElement,
|
|
2609
|
+
modifiedDate: string,
|
|
2610
|
+
modifiedStyle: Time.HumanStyle
|
|
2611
|
+
): void {
|
|
2612
|
+
let modText = '';
|
|
2613
|
+
let modTitle = '';
|
|
2614
|
+
|
|
2615
|
+
const parsedDate = new Date(modifiedDate);
|
|
2616
|
+
// Render the date in one of multiple formats, depending on the container's size
|
|
2617
|
+
modText = Time.formatHuman(parsedDate, modifiedStyle);
|
|
2618
|
+
modTitle = Time.format(parsedDate);
|
|
2619
|
+
|
|
2620
|
+
modified.textContent = modText;
|
|
2621
|
+
modified.title = modTitle;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2527
2624
|
/**
|
|
2528
2625
|
* Update an item node to reflect the current state of a model.
|
|
2529
2626
|
*
|
|
@@ -2540,7 +2637,8 @@ export namespace DirListing {
|
|
|
2540
2637
|
fileType?: DocumentRegistry.IFileType,
|
|
2541
2638
|
translator?: ITranslator,
|
|
2542
2639
|
hiddenColumns?: Set<DirListing.ToggleableColumn>,
|
|
2543
|
-
selected?: boolean
|
|
2640
|
+
selected?: boolean,
|
|
2641
|
+
modifiedStyle?: Time.HumanStyle
|
|
2544
2642
|
): void {
|
|
2545
2643
|
if (selected) {
|
|
2546
2644
|
node.classList.add(SELECTED_CLASS);
|
|
@@ -2660,14 +2758,13 @@ export namespace DirListing {
|
|
|
2660
2758
|
checkbox.checked = selected ?? false;
|
|
2661
2759
|
}
|
|
2662
2760
|
|
|
2663
|
-
let modText = '';
|
|
2664
|
-
let modTitle = '';
|
|
2665
2761
|
if (model.last_modified) {
|
|
2666
|
-
|
|
2667
|
-
|
|
2762
|
+
this.updateItemModified(
|
|
2763
|
+
modified,
|
|
2764
|
+
model.last_modified,
|
|
2765
|
+
modifiedStyle ?? 'short'
|
|
2766
|
+
);
|
|
2668
2767
|
}
|
|
2669
|
-
modified.textContent = modText;
|
|
2670
|
-
modified.title = modTitle;
|
|
2671
2768
|
}
|
|
2672
2769
|
|
|
2673
2770
|
/**
|
package/src/model.ts
CHANGED
|
@@ -234,22 +234,19 @@ export class FileBrowserModel implements IDisposable {
|
|
|
234
234
|
/**
|
|
235
235
|
* Change directory.
|
|
236
236
|
*
|
|
237
|
-
* @param path
|
|
237
|
+
* @param path The path to the file or directory.
|
|
238
238
|
*
|
|
239
239
|
* @returns A promise with the contents of the directory.
|
|
240
240
|
*/
|
|
241
|
-
async cd(
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
this._model.path,
|
|
245
|
-
newValue
|
|
246
|
-
);
|
|
241
|
+
async cd(path = '.'): Promise<void> {
|
|
242
|
+
if (path !== '.') {
|
|
243
|
+
path = this.manager.services.contents.resolvePath(this._model.path, path);
|
|
247
244
|
} else {
|
|
248
|
-
|
|
245
|
+
path = this._pendingPath || this._model.path;
|
|
249
246
|
}
|
|
250
247
|
if (this._pending) {
|
|
251
248
|
// Collapse requests to the same directory.
|
|
252
|
-
if (
|
|
249
|
+
if (path === this._pendingPath) {
|
|
253
250
|
return this._pending;
|
|
254
251
|
}
|
|
255
252
|
// Otherwise wait for the pending request to complete before continuing.
|
|
@@ -257,13 +254,13 @@ export class FileBrowserModel implements IDisposable {
|
|
|
257
254
|
}
|
|
258
255
|
const oldValue = this.path;
|
|
259
256
|
const options: Contents.IFetchOptions = { content: true };
|
|
260
|
-
this._pendingPath =
|
|
261
|
-
if (oldValue !==
|
|
257
|
+
this._pendingPath = path;
|
|
258
|
+
if (oldValue !== path) {
|
|
262
259
|
this._sessions.length = 0;
|
|
263
260
|
}
|
|
264
261
|
const services = this.manager.services;
|
|
265
262
|
this._pending = services.contents
|
|
266
|
-
.get(
|
|
263
|
+
.get(path, options)
|
|
267
264
|
.then(contents => {
|
|
268
265
|
if (this.isDisposed) {
|
|
269
266
|
return;
|
|
@@ -271,17 +268,17 @@ export class FileBrowserModel implements IDisposable {
|
|
|
271
268
|
this.handleContents(contents);
|
|
272
269
|
this._pendingPath = null;
|
|
273
270
|
this._pending = null;
|
|
274
|
-
if (oldValue !==
|
|
271
|
+
if (oldValue !== path) {
|
|
275
272
|
// If there is a state database and a unique key, save the new path.
|
|
276
273
|
// We don't need to wait on the save to continue.
|
|
277
274
|
if (this._state && this._key) {
|
|
278
|
-
void this._state.save(this._key, { path
|
|
275
|
+
void this._state.save(this._key, { path });
|
|
279
276
|
}
|
|
280
277
|
|
|
281
278
|
this._pathChanged.emit({
|
|
282
279
|
name: 'path',
|
|
283
280
|
oldValue,
|
|
284
|
-
newValue
|
|
281
|
+
newValue: path
|
|
285
282
|
});
|
|
286
283
|
}
|
|
287
284
|
this.onRunningChanged(services.sessions, services.sessions.running());
|
|
@@ -290,11 +287,7 @@ export class FileBrowserModel implements IDisposable {
|
|
|
290
287
|
.catch(error => {
|
|
291
288
|
this._pendingPath = null;
|
|
292
289
|
this._pending = null;
|
|
293
|
-
if (
|
|
294
|
-
error.response &&
|
|
295
|
-
error.response.status === 404 &&
|
|
296
|
-
newValue !== '/'
|
|
297
|
-
) {
|
|
290
|
+
if (error.response && error.response.status === 404 && path !== '/') {
|
|
298
291
|
error.message = this._trans.__(
|
|
299
292
|
'Directory not found: "%1"',
|
|
300
293
|
this._model.path
|
package/src/uploadstatus.tsx
CHANGED
|
@@ -23,7 +23,7 @@ const HALF_SPACING = 4;
|
|
|
23
23
|
/**
|
|
24
24
|
* A pure function component for a FileUpload status item.
|
|
25
25
|
*
|
|
26
|
-
* @param props
|
|
26
|
+
* @param props the props for the component.
|
|
27
27
|
*
|
|
28
28
|
* @returns a tsx component for the file upload status.
|
|
29
29
|
*/
|
package/style/base.css
CHANGED
|
@@ -99,6 +99,10 @@
|
|
|
99
99
|
display: none;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
.jp-FileBrowser-toolbar > .jp-FileBrowser-filterBox {
|
|
103
|
+
width: 100%;
|
|
104
|
+
}
|
|
105
|
+
|
|
102
106
|
.jp-Open-Dialog > .jp-FileBrowser {
|
|
103
107
|
min-height: 200px;
|
|
104
108
|
}
|
|
@@ -140,11 +144,11 @@
|
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
.jp-DirListing-headerItem.jp-id-name {
|
|
143
|
-
flex: 1
|
|
147
|
+
flex: 1 1 126px;
|
|
144
148
|
}
|
|
145
149
|
|
|
146
150
|
.jp-DirListing-headerItem.jp-id-modified {
|
|
147
|
-
flex:
|
|
151
|
+
flex: 1 0 70px;
|
|
148
152
|
border-left: var(--jp-border-width) solid var(--jp-border-color2);
|
|
149
153
|
text-align: right;
|
|
150
154
|
}
|
|
@@ -272,7 +276,7 @@
|
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
.jp-DirListing-itemText {
|
|
275
|
-
flex: 1
|
|
279
|
+
flex: 1 1 106px;
|
|
276
280
|
white-space: nowrap;
|
|
277
281
|
overflow: hidden;
|
|
278
282
|
text-overflow: ellipsis;
|
|
@@ -297,7 +301,7 @@
|
|
|
297
301
|
}
|
|
298
302
|
|
|
299
303
|
.jp-DirListing-itemModified {
|
|
300
|
-
flex:
|
|
304
|
+
flex: 1 0 83px;
|
|
301
305
|
text-align: right;
|
|
302
306
|
}
|
|
303
307
|
|