@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/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 model - The file browser view model.
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
- // Update item nodes based on widget state.
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.__('Last Modified'));
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
- modText = Time.formatHuman(new Date(model.last_modified));
2667
- modTitle = Time.format(new Date(model.last_modified));
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 - The path to the file or directory.
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(newValue = '.'): Promise<void> {
242
- if (newValue !== '.') {
243
- newValue = this.manager.services.contents.resolvePath(
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
- newValue = this._pendingPath || this._model.path;
245
+ path = this._pendingPath || this._model.path;
249
246
  }
250
247
  if (this._pending) {
251
248
  // Collapse requests to the same directory.
252
- if (newValue === this._pendingPath) {
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 = newValue;
261
- if (oldValue !== newValue) {
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(newValue, options)
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 !== newValue) {
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: newValue });
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
@@ -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: the props for the component.
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 0 84px;
147
+ flex: 1 1 126px;
144
148
  }
145
149
 
146
150
  .jp-DirListing-headerItem.jp-id-modified {
147
- flex: 0 0 112px;
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 0 64px;
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: 0 0 125px;
304
+ flex: 1 0 83px;
301
305
  text-align: right;
302
306
  }
303
307