|
@@ -2,12 +2,16 @@
|
|
|
// Distributed under the terms of the Modified BSD License.
|
|
|
|
|
|
import {
|
|
|
- Contents, Kernel, IServiceManager, Session
|
|
|
+ Contents, ContentsManager, Kernel, IServiceManager, Session
|
|
|
} from 'jupyter-js-services';
|
|
|
|
|
|
import {
|
|
|
- deepEqual
|
|
|
-} from 'phosphor/lib/algorithm/json';
|
|
|
+ ISequence
|
|
|
+} from 'phosphor/lib/algorithm/sequence';
|
|
|
+
|
|
|
+import {
|
|
|
+ Vector
|
|
|
+} from 'phosphor/lib/collections/vector';
|
|
|
|
|
|
import {
|
|
|
IDisposable
|
|
@@ -37,7 +41,7 @@ class FileBrowserModel implements IDisposable {
|
|
|
*/
|
|
|
constructor(options: FileBrowserModel.IOptions) {
|
|
|
this._manager = options.manager;
|
|
|
- this._model = { path: '', name: '/', type: 'directory', content: [] };
|
|
|
+ this._model = { path: '', name: '/', type: 'directory' };
|
|
|
this.cd();
|
|
|
this._manager.sessions.runningChanged.connect(this._onRunningChanged, this);
|
|
|
}
|
|
@@ -59,36 +63,30 @@ class FileBrowserModel implements IDisposable {
|
|
|
|
|
|
/**
|
|
|
* Get the current path.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
*/
|
|
|
get path(): string {
|
|
|
- return this._model.path;
|
|
|
+ return this._model ? this._model.path : '';
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Get a read-only list of the items in the current path.
|
|
|
*/
|
|
|
- get items(): Contents.IModel[] {
|
|
|
- return this._model.content ? this._model.content.slice() : [];
|
|
|
+ get items(): ISequence<Contents.IModel> {
|
|
|
+ return this._items;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Get whether the view model is disposed.
|
|
|
+ * Get whether the model is disposed.
|
|
|
*/
|
|
|
get isDisposed(): boolean {
|
|
|
return this._model === null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Get the session models for active notebooks.
|
|
|
- *
|
|
|
- * #### Notes
|
|
|
- * This is a read-only property.
|
|
|
+ * Get the session models for active notebooks in the current directory.
|
|
|
*/
|
|
|
- get sessions(): Session.IModel[] {
|
|
|
- return this._sessions.slice();
|
|
|
+ get sessions(): ISequence<Session.IModel> {
|
|
|
+ return this._sessions;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -99,10 +97,15 @@ class FileBrowserModel implements IDisposable {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Dispose of the resources held by the view model.
|
|
|
+ * Dispose of the resources held by the model.
|
|
|
*/
|
|
|
dispose(): void {
|
|
|
+ if (this.isDisposed) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
this._model = null;
|
|
|
+ this._sessions.clear();
|
|
|
+ this._items.clear();
|
|
|
this._manager = null;
|
|
|
clearSignalData(this);
|
|
|
}
|
|
@@ -120,7 +123,7 @@ class FileBrowserModel implements IDisposable {
|
|
|
}
|
|
|
// Collapse requests to the same directory.
|
|
|
if (newValue === this._pendingPath) {
|
|
|
- return Promise.resolve(void 0);
|
|
|
+ return this._pending;
|
|
|
}
|
|
|
let oldValue = this.path;
|
|
|
let options: Contents.IFetchOptions = { content: true };
|
|
@@ -129,13 +132,18 @@ class FileBrowserModel implements IDisposable {
|
|
|
newValue = this.path;
|
|
|
}
|
|
|
if (oldValue !== newValue) {
|
|
|
- this._sessions = [];
|
|
|
+ this._sessions.clear();
|
|
|
}
|
|
|
- this._pending = this._manager.contents.get(newValue, options).then(contents => {
|
|
|
- this._model = contents;
|
|
|
- return this._manager.sessions.listRunning();
|
|
|
+ let manager = this._manager;
|
|
|
+ this._pending = manager.contents.get(newValue, options).then(contents => {
|
|
|
+ this._handleContents(contents);
|
|
|
+ this._pendingPath = null;
|
|
|
+ return manager.sessions.listRunning();
|
|
|
}).then(models => {
|
|
|
- this._onRunningChanged(this._manager.sessions, models);
|
|
|
+ if (this.isDisposed) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this._onRunningChanged(manager.sessions, models);
|
|
|
if (oldValue !== newValue) {
|
|
|
this.pathChanged.emit({
|
|
|
name: 'path',
|
|
@@ -144,7 +152,6 @@ class FileBrowserModel implements IDisposable {
|
|
|
});
|
|
|
}
|
|
|
this.refreshed.emit(void 0);
|
|
|
- this._pendingPath = null;
|
|
|
});
|
|
|
return this._pending;
|
|
|
}
|
|
@@ -198,7 +205,7 @@ class FileBrowserModel implements IDisposable {
|
|
|
return this._manager.contents.delete(path).then(() => {
|
|
|
this.fileChanged.emit({
|
|
|
name: 'file',
|
|
|
- oldValue: {path: path},
|
|
|
+ oldValue: { path: path },
|
|
|
newValue: void 0
|
|
|
});
|
|
|
});
|
|
@@ -299,7 +306,7 @@ class FileBrowserModel implements IDisposable {
|
|
|
path = path ? path + '/' + file.name : file.name;
|
|
|
return this._manager.contents.get(path, {}).then(() => {
|
|
|
let msg = `"${file.name}" already exists`;
|
|
|
- return Private.typedThrow<Contents.IModel>(msg);
|
|
|
+ throw new Error(msg);
|
|
|
}, () => {
|
|
|
return this._upload(file);
|
|
|
});
|
|
@@ -359,25 +366,29 @@ class FileBrowserModel implements IDisposable {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Handle an updated contents model.
|
|
|
+ */
|
|
|
+ private _handleContents(contents: Contents.IModel): void {
|
|
|
+ // Update our internal data.
|
|
|
+ this._model = contents;
|
|
|
+ this._items.clear();
|
|
|
+ this._paths = contents.content.map((model: Contents.IModel) => {
|
|
|
+ return model.path;
|
|
|
+ });
|
|
|
+ this._items = new Vector<Contents.IModel>(contents.content);
|
|
|
+ this._model.content = null;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Handle a change to the running sessions.
|
|
|
*/
|
|
|
private _onRunningChanged(sender: Session.IManager, models: Session.IModel[]): void {
|
|
|
- if (deepEqual(models, this._sessions)) {
|
|
|
- return;
|
|
|
- }
|
|
|
- this._sessions = [];
|
|
|
- if (!models.length) {
|
|
|
- this.refreshed.emit(void 0);
|
|
|
- return;
|
|
|
- }
|
|
|
- let paths = this._model.content.map((contents: Contents.IModel) => {
|
|
|
- return contents.path;
|
|
|
- });
|
|
|
+ this._sessions.clear();
|
|
|
for (let model of models) {
|
|
|
- let index = paths.indexOf(model.notebook.path);
|
|
|
+ let index = this._paths.indexOf(model.notebook.path);
|
|
|
if (index !== -1) {
|
|
|
- this._sessions.push(model);
|
|
|
+ this._sessions.pushBack(model);
|
|
|
}
|
|
|
}
|
|
|
this.refreshed.emit(void 0);
|
|
@@ -385,7 +396,9 @@ class FileBrowserModel implements IDisposable {
|
|
|
|
|
|
private _maxUploadSizeMb = 15;
|
|
|
private _manager: IServiceManager = null;
|
|
|
- private _sessions: Session.IModel[] = [];
|
|
|
+ private _sessions = new Vector<Session.IModel>();
|
|
|
+ private _items = new Vector<Contents.IModel>();
|
|
|
+ private _paths: string[] = [];
|
|
|
private _model: Contents.IModel;
|
|
|
private _pendingPath: string = null;
|
|
|
private _pending: Promise<void> = null;
|
|
@@ -446,47 +459,6 @@ namespace Private {
|
|
|
*/
|
|
|
export
|
|
|
function normalizePath(root: string, path: string): string {
|
|
|
- // Current directory
|
|
|
- if (path === '.') {
|
|
|
- return root;
|
|
|
- }
|
|
|
- // Root path.
|
|
|
- if (path.indexOf('/') === 0) {
|
|
|
- path = path.slice(1, path.length);
|
|
|
- root = '';
|
|
|
- // Current directory.
|
|
|
- } else if (path.indexOf('./') === 0) {
|
|
|
- path = path.slice(2, path.length);
|
|
|
- // Grandparent directory.
|
|
|
- } else if (path.indexOf('../../') === 0) {
|
|
|
- let parts = root.split('/');
|
|
|
- root = parts.splice(0, parts.length - 2).join('/');
|
|
|
- path = path.slice(6, path.length);
|
|
|
- // Parent directory.
|
|
|
- } else if (path.indexOf('../') === 0) {
|
|
|
- let parts = root.split('/');
|
|
|
- root = parts.splice(0, parts.length - 1).join('/');
|
|
|
- path = path.slice(3, path.length);
|
|
|
- } else {
|
|
|
- // Current directory.
|
|
|
- }
|
|
|
- if (path[path.length - 1] === '/') {
|
|
|
- path = path.slice(0, path.length - 1);
|
|
|
- }
|
|
|
- // Combine the root and the path if necessary.
|
|
|
- if (root && path) {
|
|
|
- path = root + '/' + path;
|
|
|
- } else if (root) {
|
|
|
- path = root;
|
|
|
- }
|
|
|
- return path;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Work around TS 1.8 type inferencing in promises which only throw.
|
|
|
- */
|
|
|
- export
|
|
|
- function typedThrow<T>(msg: string): T {
|
|
|
- throw new Error(msg);
|
|
|
+ return ContentsManager.getAbsolutePath(path, root);
|
|
|
}
|
|
|
}
|