From 50c68f1abce1e7857a49d5c67917f6f9946b762d Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 14 Sep 2017 14:23:29 -0700 Subject: [PATCH 01/28] Show tabs and swtich working --- .../file/browse/file-browse.module.ts | 6 ++- .../file-explorer-tabs.component.ts | 42 +++++++++++++++++++ .../file-explorer-tabs.html | 8 ++++ .../file-explorer-tabs.scss | 33 +++++++++++++++ .../file-explorer/file-explorer-tabs/index.ts | 1 + .../file-explorer/file-explorer.component.ts | 23 +++++++++- .../browse/file-explorer/file-explorer.html | 1 + .../file/browse/file-explorer/index.ts | 1 + app/components/task/details/task-details.html | 13 ++---- 9 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts create mode 100644 app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html create mode 100644 app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss create mode 100644 app/components/file/browse/file-explorer/file-explorer-tabs/index.ts diff --git a/app/components/file/browse/file-browse.module.ts b/app/components/file/browse/file-browse.module.ts index ab25aeb49d..ec4ce9cb15 100644 --- a/app/components/file/browse/file-browse.module.ts +++ b/app/components/file/browse/file-browse.module.ts @@ -3,14 +3,16 @@ import { commonModules } from "app/common"; import { FileDetailsModule } from "app/components/file/details"; import { BlobFilesBrowserComponent } from "./blob-files-browser"; import { FileDirectoryFilter, FileListDisplayComponent } from "./display"; -import { FileExplorerComponent, FileTableViewComponent, FileTreeViewComponent } from "./file-explorer"; +import { + FileExplorerComponent, FileExplorerTabsComponent, FileTableViewComponent, FileTreeViewComponent, +} from "./file-explorer"; import { NodeFileBrowseComponent } from "./node-file-browse.component"; const components = [ FileDirectoryFilter, FileTreeViewComponent, FileListDisplayComponent, NodeFileBrowseComponent, BlobFilesBrowserComponent, - FileExplorerComponent, FileTableViewComponent, + FileExplorerComponent, FileTableViewComponent, FileExplorerTabsComponent, ]; @NgModule({ diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts new file mode 100644 index 0000000000..bfe47b4187 --- /dev/null +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -0,0 +1,42 @@ +import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core"; +import * as path from "path"; +import "./file-explorer-tabs.scss"; + +interface Tab { + filename: string; + displayName: string; +} + +@Component({ + selector: "bl-file-explorer-tabs", + templateUrl: "file-explorer-tabs.html", +}) +export class FileExplorerTabsComponent implements OnChanges { + @Input() public activeFile = null; + @Output() public activeFileChange = new EventEmitter(); + + @Input() public openedFiles: string[] = []; + + public tabs: Tab[] = []; + + public ngOnChanges(changes) { + if (changes.openedFiles) { + this._updateTabs(); + } + } + + public activateTab(tab: Tab) { + const filename = tab && tab.filename; + this.activeFile = filename; + this.activeFileChange.emit(filename); + } + + private _updateTabs() { + this.tabs = this.openedFiles.map((filename) => { + return { + filename, + displayName: path.basename(filename), + }; + }); + } +} diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html new file mode 100644 index 0000000000..a9eb833e68 --- /dev/null +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -0,0 +1,8 @@ +
+
+ +
+
+ {{tab.displayName}} +
+
diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss new file mode 100644 index 0000000000..12e3401e89 --- /dev/null +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -0,0 +1,33 @@ +@import "app/styles/variables"; + +bl-file-explorer-tabs { + $tab-height: 35px; + display: block; + .tablist { + height: $tab-height; + // border-bottom: 1px solid $border-color; + background: #e5e5e5; + display: flex; + + > .tab { + display: flex; + align-items: center; + height: $tab-height; + min-width: fit-content; + white-space: nowrap; + cursor: pointer; + padding-left: 10px; + color: $dove-grey; + border-right: 1px solid $border-color; + padding: 0 5px; + } + + > .tab.main { + font-size: 24px; + } + + > .tab.active { + background: $whitesmoke; + } + } +} diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts new file mode 100644 index 0000000000..d1921e96c2 --- /dev/null +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts @@ -0,0 +1 @@ +export * from "./file-explorer-tabs.component" diff --git a/app/components/file/browse/file-explorer/file-explorer.component.ts b/app/components/file/browse/file-explorer/file-explorer.component.ts index ec6f020189..59763dc5e2 100644 --- a/app/components/file/browse/file-explorer/file-explorer.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer.component.ts @@ -4,6 +4,7 @@ import { Subscription } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; import { FileNavigator, FileTreeNode } from "app/services/file"; import "./file-explorer.scss"; +import { exists } from "app/utils"; export interface FileNavigatorEntry { name: string; @@ -71,9 +72,10 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { public currentNode: FileTreeNode; public currentFileNavigator: FileNavigator; public currentFileNavigatorEntry: FileNavigatorEntry; + public openedFiles: string[] = ["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"]; + private _lastFolderExplored: string = ""; private _currentNodeSub: Subscription; - private _config: FileExplorerConfig = fileExplorerDefaultConfig; public ngOnChanges(inputs) { @@ -111,6 +113,21 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { this._updateNavigatorEvents(); } + /** + * Triggered when a tab select to open a file + * @param filename File to open + * + * If filename is null or undefined it will show the file table viewer at the last position + */ + public openFile(filename?: string) { + this.activeFile = filename; + if (exists(filename)) { + this.navigateTo(filename); + } else { + this.navigateTo(this._lastFolderExplored); + } + } + public goBack() { this.currentFileNavigator.goBack(); } @@ -121,8 +138,12 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { private _updateNavigatorEvents() { this._clearCurrentNodeSub(); + this._lastFolderExplored = ""; this._currentNodeSub = this.currentFileNavigator.currentNode.subscribe((node) => { this.currentNode = node; + if (node.isDirectory) { + this._lastFolderExplored = node.path; + } }); } diff --git a/app/components/file/browse/file-explorer/file-explorer.html b/app/components/file/browse/file-explorer/file-explorer.html index 23354b5e7c..9bc26ce517 100644 --- a/app/components/file/browse/file-explorer/file-explorer.html +++ b/app/components/file/browse/file-explorer/file-explorer.html @@ -9,6 +9,7 @@
+ - - Log Output -
- -
+ + Task Outputs + Configuration @@ -37,10 +35,7 @@ - - Task Outputs - - + Sub Tasks
From 08cd2ea3a810bb744ac82caeef69435b91052acf Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 14 Sep 2017 15:10:53 -0700 Subject: [PATCH 02/28] Working --- .../file-explorer-tabs.component.ts | 59 ++++++++++++----- .../file-explorer-tabs.html | 4 +- .../file-explorer/file-explorer.component.ts | 23 +------ .../browse/file-explorer/file-explorer.html | 9 ++- .../details/output/task-outputs.component.ts | 1 + .../file/file-navigator/file-navigator.ts | 64 ++++++++++++++++++- .../file/file-navigator/file-tree.model.ts | 6 ++ 7 files changed, 122 insertions(+), 44 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index bfe47b4187..866e18d24e 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -1,5 +1,7 @@ -import { Component, EventEmitter, Input, OnChanges, Output } from "@angular/core"; +import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; +import { FileNavigator, OpenedFile } from "app/services/file"; import * as path from "path"; +import { Subscription } from "rxjs/Rx"; import "./file-explorer-tabs.scss"; interface Tab { @@ -11,31 +13,58 @@ interface Tab { selector: "bl-file-explorer-tabs", templateUrl: "file-explorer-tabs.html", }) -export class FileExplorerTabsComponent implements OnChanges { - @Input() public activeFile = null; - @Output() public activeFileChange = new EventEmitter(); - - @Input() public openedFiles: string[] = []; - +export class FileExplorerTabsComponent implements OnChanges, OnDestroy { + @Input() public fileNavigator: FileNavigator; + public openedFiles: OpenedFile[] = []; public tabs: Tab[] = []; + public activePath: string = null; + + private _fileNavigatorSubs: Subscription[] = []; + private _lastFolderExplored: string = ""; public ngOnChanges(changes) { - if (changes.openedFiles) { - this._updateTabs(); + if (changes.fileNavigator) { + this._updateFileNavigatorEvents(); } } + public ngOnDestroy() { + this._clearFileNavigatorSub(); + } + public activateTab(tab: Tab) { - const filename = tab && tab.filename; - this.activeFile = filename; - this.activeFileChange.emit(filename); + if (tab) { + this.fileNavigator.navigateTo(tab.filename); + } else { + this.fileNavigator.navigateTo(this._lastFolderExplored); + } + } + + private _updateFileNavigatorEvents() { + this._lastFolderExplored = ""; + this._fileNavigatorSubs.push(this.fileNavigator._openedFiles.subscribe((files) => { + this.openedFiles = files; + this._updateTabs(); + })); + this._fileNavigatorSubs.push(this.fileNavigator.currentNode.subscribe((node) => { + if (node.isDirectory) { + this.activePath = null; + this._lastFolderExplored = node.path; + } else { + this.activePath = node.path; + } + })); + } + + private _clearFileNavigatorSub() { + this._fileNavigatorSubs.forEach(x => x.unsubscribe()); } private _updateTabs() { - this.tabs = this.openedFiles.map((filename) => { + this.tabs = this.openedFiles.map((file) => { return { - filename, - displayName: path.basename(filename), + filename: file.path, + displayName: path.basename(file.path), }; }); } diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index a9eb833e68..a07493b727 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -1,8 +1,8 @@
-
+
-
+
{{tab.displayName}}
diff --git a/app/components/file/browse/file-explorer/file-explorer.component.ts b/app/components/file/browse/file-explorer/file-explorer.component.ts index 59763dc5e2..0079b22158 100644 --- a/app/components/file/browse/file-explorer/file-explorer.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer.component.ts @@ -3,8 +3,8 @@ import { Subscription } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; import { FileNavigator, FileTreeNode } from "app/services/file"; -import "./file-explorer.scss"; import { exists } from "app/utils"; +import "./file-explorer.scss"; export interface FileNavigatorEntry { name: string; @@ -72,9 +72,7 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { public currentNode: FileTreeNode; public currentFileNavigator: FileNavigator; public currentFileNavigatorEntry: FileNavigatorEntry; - public openedFiles: string[] = ["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"]; - private _lastFolderExplored: string = ""; private _currentNodeSub: Subscription; private _config: FileExplorerConfig = fileExplorerDefaultConfig; @@ -113,21 +111,6 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { this._updateNavigatorEvents(); } - /** - * Triggered when a tab select to open a file - * @param filename File to open - * - * If filename is null or undefined it will show the file table viewer at the last position - */ - public openFile(filename?: string) { - this.activeFile = filename; - if (exists(filename)) { - this.navigateTo(filename); - } else { - this.navigateTo(this._lastFolderExplored); - } - } - public goBack() { this.currentFileNavigator.goBack(); } @@ -138,12 +121,8 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { private _updateNavigatorEvents() { this._clearCurrentNodeSub(); - this._lastFolderExplored = ""; this._currentNodeSub = this.currentFileNavigator.currentNode.subscribe((node) => { this.currentNode = node; - if (node.isDirectory) { - this._lastFolderExplored = node.path; - } }); } diff --git a/app/components/file/browse/file-explorer/file-explorer.html b/app/components/file/browse/file-explorer/file-explorer.html index 9bc26ce517..1aa4e02bc6 100644 --- a/app/components/file/browse/file-explorer/file-explorer.html +++ b/app/components/file/browse/file-explorer/file-explorer.html @@ -9,7 +9,7 @@
- + - + + +
diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index d91d5ae4e9..c45b4313bb 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -59,6 +59,7 @@ export class TaskOutputsComponent implements OnChanges { const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.taskId, { onError: (error) => this._processTaskFilesError(error), }); + nodeNavigator.openFiles(["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"]); nodeNavigator.init(); const taskOutputPrefix = `${this.taskId}/$TaskOutput/`; diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 423bda64c5..830c46f276 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -6,7 +6,7 @@ import { File, ServerError } from "app/models"; import { RxListProxy } from "app/services/core"; import { FileLoader } from "app/services/file"; import { CloudPathUtils, ObjectUtils } from "app/utils"; -import { FileTreeNode, FileTreeStructure } from "./file-tree.model"; +import { FileTreeNode, FileTreeStructure, OpenedFile } from "./file-tree.model"; export interface FileNavigatorConfig { /** @@ -43,12 +43,29 @@ export interface FileNavigatorConfig { * This can be extended for a node, task or blob file list */ export class FileNavigator { + /** + * Path of the file/directory currently being viewed + */ public currentPath: Observable; + + /** + * Tree node that is currently being viewed + */ public currentNode: Observable; public loadingStatus = LoadingStatus.Ready; public basePath: string; public tree: Observable; + + /** + * Current file loader to display + */ public currentFileLoader: FileLoader; + + /** + * List of files opended for a quick switch + */ + public openedFiles: Observable; + public _openedFiles = new BehaviorSubject([]); public error: ServerError; private _currentPath = new BehaviorSubject(""); @@ -72,6 +89,7 @@ export class FileNavigator { return tree.getNode(path).clone(); }).shareReplay(1); this.tree = this._tree.asObservable(); + this.openedFiles = this._openedFiles.asObservable(); } /** @@ -81,7 +99,11 @@ export class FileNavigator { this._loadFileInPath(); } - public navigateTo(path: string) { + /** + * @param path Path of the file/directory to navigate to + * @param openInNewTab If its the path to a file it will open the file in a new tab + */ + public navigateTo(path: string, openInNewTab: boolean = true) { if (this._currentPath.value === path) { return; } this._history.push(this._currentPath.value); this._currentPath.next(path); @@ -91,8 +113,44 @@ export class FileNavigator { this._loadFileInPath(path); } } else { - this.currentFileLoader = this._getFileLoader(node.path); + this.openFile(path, openInNewTab); + } + } + + public isFileOpen(path: string): boolean { + return Boolean(this._openedFiles.value.find(x => x.path === path)); + } + + public openFile(path: string, openInNewTab: boolean = true) { + const openedFiles = this._openedFiles.value; + if (!this.isFileOpen(path)) { + if (openInNewTab) { + openedFiles.push({ + path, + fileLoader: this._getFileLoader(path), + }); + } + } + this._openedFiles.next(openedFiles); + } + + /** + * Triggered when a tab select to open a file + * @param filename File to open + * + * If filename is null or undefined it will show the file table viewer at the last position + */ + public openFiles(paths: string[]) { + const openedFiles = this._openedFiles.value; + for (let path of paths) { + if (!this.isFileOpen(path)) { + openedFiles.push({ + path, + fileLoader: this._getFileLoader(path), + }); + } } + this._openedFiles.next(openedFiles); } /** diff --git a/app/services/file/file-navigator/file-tree.model.ts b/app/services/file/file-navigator/file-tree.model.ts index 3959f20bc0..f6ff4b7300 100644 --- a/app/services/file/file-navigator/file-tree.model.ts +++ b/app/services/file/file-navigator/file-tree.model.ts @@ -5,6 +5,7 @@ import { LoadingStatus } from "app/components/base/loading"; import { File } from "app/models"; import { CloudPathUtils } from "app/utils"; import { fileToTreeNode, generateDir, sortTreeNodes } from "./helper"; +import { FileLoader } from "app/services/file"; export interface FileTreeNodeParams { path: string; @@ -150,3 +151,8 @@ export class FileTreeStructure { } } } + +export interface OpenedFile { + path: string; // Fullpath + fileLoader: FileLoader; +} From 6cb0110f0c6c169d993d357ea8737186e58dd214 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 14 Sep 2017 16:30:02 -0700 Subject: [PATCH 03/28] Added close tabs --- .../file-explorer-tabs.component.ts | 11 ++++++++++ .../file-explorer-tabs.html | 6 +++-- .../file-explorer-tabs.scss | 22 +++++++++++++++++-- .../file/file-navigator/file-navigator.ts | 5 +++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 866e18d24e..0012713c69 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -32,6 +32,17 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { this._clearFileNavigatorSub(); } + public handleMouseDown(event: MouseEvent, tab) { + if (event.which === 2) { // Middle click + this.closeTab(event, tab); + } + } + + public closeTab(event: MouseEvent, tab: Tab) { + event.stopPropagation(); + this.fileNavigator.closeFile(tab.filename); + } + public activateTab(tab: Tab) { if (tab) { this.fileNavigator.navigateTo(tab.filename); diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index a07493b727..707d88f959 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -2,7 +2,9 @@
-
- {{tab.displayName}} +
+ {{tab.displayName}} +
diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss index 12e3401e89..4592a2e648 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -1,7 +1,7 @@ @import "app/styles/variables"; bl-file-explorer-tabs { - $tab-height: 35px; + $tab-height: 28px; display: block; .tablist { height: $tab-height; @@ -19,15 +19,33 @@ bl-file-explorer-tabs { padding-left: 10px; color: $dove-grey; border-right: 1px solid $border-color; - padding: 0 5px; } > .tab.main { font-size: 24px; + padding: 5px; } > .tab.active { background: $whitesmoke; } + + > .tab .tab-close { + width: 18px; + padding: 0 3px; + + > .close-icon { + color: $dove-grey; + opacity: 0; + + &:hover { + color: $mineshaft-grey; + } + } + } + + > .tab:hover .tab-close > .close-icon { + opacity: 1; + } } } diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 830c46f276..10a93bebd8 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -153,6 +153,11 @@ export class FileNavigator { this._openedFiles.next(openedFiles); } + public closeFile(path: string) { + const newOpenedFiles = this._openedFiles.value.filter(x => x.path !== path); + this._openedFiles.next(newOpenedFiles); + } + /** * Go back up one level */ From 88035f90565cb5ddacf9dddf8ec3c6ad50c7ec32 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 14 Sep 2017 16:48:58 -0700 Subject: [PATCH 04/28] Style tweak --- .../file-explorer/file-explorer-tabs/file-explorer-tabs.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss index 4592a2e648..19bda7d3c7 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -44,6 +44,7 @@ bl-file-explorer-tabs { } } + > .tab.active .tab-close > .close-icon, > .tab:hover .tab-close > .close-icon { opacity: 1; } From 055e943c7523fbd6a645250085bab9f8b99f9632 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 09:17:20 -0700 Subject: [PATCH 05/28] Tweak style --- app/components/base/editor/editor.component.ts | 17 ++++++++++++++--- app/components/base/editor/editor.html | 3 --- app/components/base/editor/editor.scss | 2 ++ .../file-explorer/file-explorer.component.ts | 1 - .../browse/file-explorer/file-explorer.scss | 7 +++++++ .../file/details/file-content.component.ts | 4 ++++ app/components/file/details/file-content.html | 13 +++++-------- app/components/file/details/file-content.scss | 6 ++++++ .../file-details-view/file-details-view.html | 2 +- .../file-details-view/file-details-view.scss | 15 ++++++++++++++- .../pool/graphs/nodes-heatmap.component.ts | 3 +-- 11 files changed, 54 insertions(+), 19 deletions(-) diff --git a/app/components/base/editor/editor.component.ts b/app/components/base/editor/editor.component.ts index 504d72b542..c48b8236ae 100644 --- a/app/components/base/editor/editor.component.ts +++ b/app/components/base/editor/editor.component.ts @@ -1,9 +1,10 @@ import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, - HostListener, Input, OnChanges, OnDestroy, Output, ViewChild, forwardRef, + AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, + EventEmitter, HostListener, Input, OnChanges, OnDestroy, Output, ViewChild, forwardRef, } from "@angular/core"; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; import * as CodeMirror from "codemirror"; +import * as elementResizeDetectorMaker from "element-resize-detector"; import { Observable, Subscription } from "rxjs"; import "codemirror/addon/comment/comment"; @@ -49,6 +50,7 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC public placeholder: string; private _value = ""; private _sub: Subscription; + private _erd: any; get value() { return this._value; } @@ -58,7 +60,7 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC } } - constructor(private changeDetector: ChangeDetectorRef) { } + constructor(private changeDetector: ChangeDetectorRef, private elementRef: ElementRef) { } public ngOnChanges(changes) { if (changes.config) { @@ -66,6 +68,14 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC } } public ngAfterViewInit() { + this._erd = elementResizeDetectorMaker({ + strategy: "scroll", + }); + + this._erd.listenTo(this.elementRef.nativeElement, (element) => { + this.instance.refresh(); + }); + this.config = this.config || {}; if (!this.config.extraKeys) { this.config.extraKeys = {}; @@ -76,6 +86,7 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC public ngOnDestroy() { this._sub.unsubscribe(); + this._erd.uninstall(this.elementRef.nativeElement); } public codemirrorInit(config) { diff --git a/app/components/base/editor/editor.html b/app/components/base/editor/editor.html index c722809e14..2a0033ee43 100644 --- a/app/components/base/editor/editor.html +++ b/app/components/base/editor/editor.html @@ -1,4 +1 @@ -
- -
diff --git a/app/components/base/editor/editor.scss b/app/components/base/editor/editor.scss index 942124e0bd..77a3f74819 100644 --- a/app/components/base/editor/editor.scss +++ b/app/components/base/editor/editor.scss @@ -1,4 +1,6 @@ bl-editor { + display: block; + height: 100%; .CodeMirror { height: auto; } diff --git a/app/components/file/browse/file-explorer/file-explorer.component.ts b/app/components/file/browse/file-explorer/file-explorer.component.ts index 0079b22158..e6636eb01c 100644 --- a/app/components/file/browse/file-explorer/file-explorer.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer.component.ts @@ -3,7 +3,6 @@ import { Subscription } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; import { FileNavigator, FileTreeNode } from "app/services/file"; -import { exists } from "app/utils"; import "./file-explorer.scss"; export interface FileNavigatorEntry { diff --git a/app/components/file/browse/file-explorer/file-explorer.scss b/app/components/file/browse/file-explorer/file-explorer.scss index 709954a2ff..c73a752e4f 100644 --- a/app/components/file/browse/file-explorer/file-explorer.scss +++ b/app/components/file/browse/file-explorer/file-explorer.scss @@ -35,6 +35,13 @@ bl-file-explorer { flex: 1; position: relative; overflow: hidden; + + display: flex; + flex-direction: column; + + > bl-file-details-view, bl-file-table-view { + flex: 1; + } } diff --git a/app/components/file/details/file-content.component.ts b/app/components/file/details/file-content.component.ts index 43a94a4e50..b4477e498f 100644 --- a/app/components/file/details/file-content.component.ts +++ b/app/components/file/details/file-content.component.ts @@ -2,6 +2,7 @@ import { Component, Input, OnChanges, SimpleChanges } from "@angular/core"; import { SettingsService } from "app/services"; import { FileLoader } from "app/services/file"; +import { log } from "app/utils"; import "./file-content.scss"; enum FileType { @@ -24,6 +25,9 @@ export class FileContentComponent implements OnChanges { constructor(private settingsService: SettingsService) { } public ngOnChanges(changes: SimpleChanges) { if (changes.fileLoader) { + if (!this.fileLoader) { + log.error("FileContentComponent fileLoader input is required but is", this.fileLoader); + } this._findFileType(); } } diff --git a/app/components/file/details/file-content.html b/app/components/file/details/file-content.html index 5634a65131..220cafc694 100644 --- a/app/components/file/details/file-content.html +++ b/app/components/file/details/file-content.html @@ -1,14 +1,11 @@ -
- Error this file is neither a node, task or blob file. -
-
-
+ +
-
+
-
+
@@ -19,4 +16,4 @@

This file is not a known extension. Do you still want to open

-
+
diff --git a/app/components/file/details/file-content.scss b/app/components/file/details/file-content.scss index 60292e9672..33067ac3fa 100644 --- a/app/components/file/details/file-content.scss +++ b/app/components/file/details/file-content.scss @@ -1,6 +1,12 @@ @import "app/styles/variables"; bl-file-content { + .viewer-container { + height: 100%; + > * { + height: 100%; + } + } .unknown-file-type { display: flex; align-items: center; diff --git a/app/components/file/details/file-details-view/file-details-view.html b/app/components/file/details/file-details-view/file-details-view.html index 6fed0991c6..44dd8e869f 100644 --- a/app/components/file/details/file-details-view/file-details-view.html +++ b/app/components/file/details/file-details-view/file-details-view.html @@ -1,4 +1,4 @@ -
+
diff --git a/app/components/file/details/file-details-view/file-details-view.scss b/app/components/file/details/file-details-view/file-details-view.scss index 2fa25567f5..c12353d564 100644 --- a/app/components/file/details/file-details-view/file-details-view.scss +++ b/app/components/file/details/file-details-view/file-details-view.scss @@ -2,6 +2,19 @@ bl-file-details-view { $file-header-height: 40px; + $separator-height: 5px; + .file-details-container { + height: 100%; + + > .details { + height: calc(100% - #{$file-header-height + $separator-height}); + + > bl-file-content { + height: 100%; + display: block; + } + } + } .file-header { display: flex; @@ -9,7 +22,7 @@ bl-file-details-view { align-items: stretch; color: $mineshaft-grey; border-bottom: 1px solid $border-color; - margin-bottom: 5px; + margin-bottom: $separator-height; > * { padding: 0 10px; &:not(:last-child) { diff --git a/app/components/pool/graphs/nodes-heatmap.component.ts b/app/components/pool/graphs/nodes-heatmap.component.ts index c21c11dca8..e32ad02447 100644 --- a/app/components/pool/graphs/nodes-heatmap.component.ts +++ b/app/components/pool/graphs/nodes-heatmap.component.ts @@ -114,7 +114,6 @@ export class NodesHeatmapComponent implements AfterViewInit, OnChanges, OnDestro private _nodeMap: { [id: string]: Node } = {}; constructor( - private elementRef: ElementRef, private contextMenuService: ContextMenuService, private nodeService: NodeService, private sidebarManager: SidebarManager, @@ -180,7 +179,7 @@ export class NodesHeatmapComponent implements AfterViewInit, OnChanges, OnDestro } public ngOnDestroy() { - this._erd.uninstall(this.elementRef.nativeElement); + this._erd.uninstall(this.heatmapEl.nativeElement); } public containerSizeChanged() { From 4a800e55d87b5335df364a30c4c3f8463170869d Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 09:57:12 -0700 Subject: [PATCH 06/28] Don't show file if task is active of preparing --- .../details/output/task-outputs.component.ts | 51 +++++++++++++++---- .../task/details/output/task-outputs.html | 6 ++- .../task/details/output/task-outputs.scss | 25 +++++++++ app/components/task/details/task-details.html | 2 +- app/utils/component-utils.ts | 14 +++++ app/utils/index.ts | 1 + 6 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 app/utils/component-utils.ts diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index c45b4313bb..fbfb1e7e4d 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -1,9 +1,9 @@ import { Component, Input, OnChanges } from "@angular/core"; import { FileNavigatorEntry } from "app/components/file/browse/file-explorer"; -import { ServerError } from "app/models"; +import { ServerError, Task, TaskState } from "app/models"; import { FileService, StorageService } from "app/services"; import { FileLoader } from "app/services/file"; -import { Constants, StorageUtils } from "app/utils"; +import { ComponentUtils, Constants, StorageUtils } from "app/utils"; import "./task-outputs.scss"; enum OutputType { @@ -24,7 +24,7 @@ const outputTabs = [ export class TaskOutputsComponent implements OnChanges { @Input() public jobId: string; - @Input() public taskId: string; + @Input() public task: Task; public OutputType = OutputType; public outputTabs = outputTabs; @@ -32,14 +32,29 @@ export class TaskOutputsComponent implements OnChanges { public pickedFileLoader: FileLoader = null; public fileNavigators: FileNavigatorEntry[] = []; + public isTaskQueued = false; + public stateTooltip: string; private _taskOutputContainer: string; constructor(private fileService: FileService, private storageService: StorageService) { } - public ngOnChanges(inputs) { - this._clearFileNavigator(); - if (inputs.jobId || inputs.taskId) { + public ngOnChanges(changes) { + let updateNavigator = false; + if (changes.task) { + const isTaskQueued = this.task.state === TaskState.active || this.task.state === TaskState.preparing; + if (isTaskQueued !== this.isTaskQueued) { + updateNavigator = true; + } + this.isTaskQueued = isTaskQueued; + this._updateStateTooltip(); + } + if (changes.jobId || ComponentUtils.recordChangedId(changes.task)) { + if (!this.isTaskQueued) { + updateNavigator = true; + } + } + if (updateNavigator) { this._updateNavigator(); } } @@ -49,26 +64,26 @@ export class TaskOutputsComponent implements OnChanges { } public updatePickedFile(filename) { - this.pickedFileLoader = this.fileService.fileFromTask(this.jobId, this.taskId, filename); + this.pickedFileLoader = this.fileService.fileFromTask(this.jobId, this.task.id, filename); } private _updateNavigator() { StorageUtils.getSafeContainerName(this.jobId).then((container) => { this._taskOutputContainer = container; this._clearFileNavigator(); - const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.taskId, { + const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.task.id, { onError: (error) => this._processTaskFilesError(error), }); nodeNavigator.openFiles(["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"]); nodeNavigator.init(); - const taskOutputPrefix = `${this.taskId}/$TaskOutput/`; + const taskOutputPrefix = `${this.task.id}/$TaskOutput/`; const taskOutputNavigator = this.storageService.navigateContainerBlobs(container, taskOutputPrefix, { onError: (error) => this._processBlobError(error), }); taskOutputNavigator.init(); - const taskLogsPrefix = `${this.taskId}/$TaskLog/`; + const taskLogsPrefix = `${this.task.id}/$TaskLog/`; const taskLogsNavigator = this.storageService.navigateContainerBlobs(container, taskLogsPrefix, { onError: (error) => this._processBlobError(error), }); @@ -104,6 +119,7 @@ export class TaskOutputsComponent implements OnChanges { } return error; } + private _processBlobError(error: ServerError): ServerError { if (error.status === Constants.HttpCode.NotFound && error.body.code === "ContainerNotFound") { return new ServerError({ @@ -120,6 +136,7 @@ export class TaskOutputsComponent implements OnChanges { private _clearFileNavigator() { this.fileNavigators.forEach(x => x.navigator.dispose()); + this.fileNavigators = []; } private _fileConventionErrorMessage() { @@ -127,4 +144,18 @@ export class TaskOutputsComponent implements OnChanges { + `There is no blob container with the name '${this._taskOutputContainer}'\n` + `Learn more here https://docs.microsoft.com/en-us/azure/batch/batch-task-output-file-conventions`; } + + private _updateStateTooltip() { + switch (this.task.state) { + case TaskState.active: + this.stateTooltip = "Task is queued and is waiting to be scheduled by Azure Batch"; + break; + case TaskState.preparing: + this.stateTooltip = "Task has been scheduled to run and " + + "is waiting for the job preparation task to complete"; + break; + default: + this.stateTooltip = null; + } + } } diff --git a/app/components/task/details/output/task-outputs.html b/app/components/task/details/output/task-outputs.html index e4bdb91046..5bc8a16f83 100644 --- a/app/components/task/details/output/task-outputs.html +++ b/app/components/task/details/output/task-outputs.html @@ -1,2 +1,6 @@ - + +
+ Task is currently  {{task.state}}  there is no output + to view yet. +
diff --git a/app/components/task/details/output/task-outputs.scss b/app/components/task/details/output/task-outputs.scss index 7c953d0ddf..8798621813 100644 --- a/app/components/task/details/output/task-outputs.scss +++ b/app/components/task/details/output/task-outputs.scss @@ -2,4 +2,29 @@ bl-task-outputs { height: 100%; + + > .task-queued { + $loading-overlay-width: 500px; + $loading-overlay-height: 70px; + width: $loading-overlay-width; + height: $loading-overlay-height; + position: absolute; + top: calc(50% - #{$loading-overlay-height/2}); + left: calc(50% - #{$loading-overlay-width/2}); + + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + + background: $mineshaft-grey; + color: white; + opacity: 0.5; + padding: 10px; + + > .state { + color: map-get($primary, 200); + font-weight: 700; + } + } } diff --git a/app/components/task/details/task-details.html b/app/components/task/details/task-details.html index 717c6928a7..62e89201e2 100644 --- a/app/components/task/details/task-details.html +++ b/app/components/task/details/task-details.html @@ -23,7 +23,7 @@ Task Outputs - + Configuration diff --git a/app/utils/component-utils.ts b/app/utils/component-utils.ts new file mode 100644 index 0000000000..b42d2b95a6 --- /dev/null +++ b/app/utils/component-utils.ts @@ -0,0 +1,14 @@ +import { SimpleChange } from "@angular/core"; + +export class ComponentUtils { + /** + * Return true if the record changed id + * @param change Simple change from ngOnChanges + */ + public static recordChangedId(change: SimpleChange): boolean { + if (!change) { return false; } + const { previousValue, currentValue } = change; + const same = previousValue && currentValue && previousValue.id === currentValue.id; + return !same; + } +} diff --git a/app/utils/index.ts b/app/utils/index.ts index 681c7cc5d1..3681e80058 100644 --- a/app/utils/index.ts +++ b/app/utils/index.ts @@ -2,6 +2,7 @@ export * from "./array"; export * from "./color"; export * from "./cloud-path-utils"; +export * from "./component-utils"; export * from "./date-utils"; export * from "./drag-utils"; export * from "./file-url-utils"; From c55ba886a9e927e9c3976d5bcdeefb4e92c5f26f Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 11:34:03 -0700 Subject: [PATCH 07/28] File not found --- .../browse/file-explorer/file-explorer.html | 2 +- .../code-file-viewer.component.ts | 34 +++++---- .../file-details-view.component.ts | 22 ++++-- .../file-details-view/file-details-view.html | 5 +- .../task/details/output/task-outputs.html | 4 +- .../task/details/output/task-outputs.scss | 25 ------- app/services/file-service.ts | 2 +- .../file/file-navigator/file-navigator.ts | 75 ++++++++++++++++--- .../file/file-navigator/file-tree.model.ts | 7 +- app/styles/partials/loading-indicators.scss | 24 ++++++ 10 files changed, 137 insertions(+), 63 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer.html b/app/components/file/browse/file-explorer/file-explorer.html index 1aa4e02bc6..b3d22153e8 100644 --- a/app/components/file/browse/file-explorer/file-explorer.html +++ b/app/components/file/browse/file-explorer/file-explorer.html @@ -11,7 +11,7 @@
- { - this.file = file; - const contentLength = file.properties.contentLength; - if (contentLength > maxSize) { - this.fileTooLarge = true; - this.loadingStatus = LoadingStatus.Ready; - return; - } - - this.fileLoader.content().subscribe((result) => { - this.value = result.content.toString(); - this.loadingStatus = LoadingStatus.Ready; - }); + this.fileNotFound = false; + this.fileLoader.getProperties().subscribe({ + next: (file: File) => { + this.file = file; + const contentLength = file.properties.contentLength; + if (contentLength > maxSize) { + this.fileTooLarge = true; + this.loadingStatus = LoadingStatus.Ready; + return; + } + + this.fileLoader.content().subscribe((result) => { + this.value = result.content.toString(); + this.loadingStatus = LoadingStatus.Ready; + }); + }, + error: (error) => null, }); } diff --git a/app/components/file/details/file-details-view/file-details-view.component.ts b/app/components/file/details/file-details-view/file-details-view.component.ts index 96712344e0..9c05fbf979 100644 --- a/app/components/file/details/file-details-view/file-details-view.component.ts +++ b/app/components/file/details/file-details-view/file-details-view.component.ts @@ -9,7 +9,7 @@ import { NotificationService } from "app/components/base/notifications"; import { File, ServerError } from "app/models"; import { ElectronShell } from "app/services"; import { FileLoader } from "app/services/file"; -import { DateUtils, prettyBytes } from "app/utils"; +import { Constants, DateUtils, log, prettyBytes } from "app/utils"; @Component({ selector: "bl-file-details-view", @@ -21,6 +21,7 @@ export class FileDetailsViewComponent implements OnChanges { public filename: string; public file: File; + public fileNotFound = false; public contentSize: string = "-"; public lastModified: string = "-"; public downloadEnabled: boolean; @@ -91,12 +92,23 @@ export class FileDetailsViewComponent implements OnChanges { private _updateFileProperties(forceNew = false): Observable { this.contentSize = "-"; this.lastModified = "-"; + this.fileNotFound = false; const obs = this.fileLoader.getProperties(forceNew); - obs.subscribe((file: File) => { - this.file = file; - this.contentSize = prettyBytes(file.properties.contentLength); - this.lastModified = DateUtils.prettyDate(file.properties.lastModified); + obs.subscribe({ + next: (file: File) => { + this.file = file; + this.contentSize = prettyBytes(file.properties.contentLength); + this.lastModified = DateUtils.prettyDate(file.properties.lastModified); + }, + error: (error: ServerError) => { + if (error.status === Constants.HttpCode.NotFound) { + this.fileNotFound = true; + } else { + log.error(`Error loading file ${this.filename}`, error); + } + }, }); + return obs; } } diff --git a/app/components/file/details/file-details-view/file-details-view.html b/app/components/file/details/file-details-view/file-details-view.html index 44dd8e869f..d0593ae0fa 100644 --- a/app/components/file/details/file-details-view/file-details-view.html +++ b/app/components/file/details/file-details-view/file-details-view.html @@ -20,7 +20,10 @@
-
+
+
+ File  {{filename}}  doesn't exists. +
diff --git a/app/components/task/details/output/task-outputs.html b/app/components/task/details/output/task-outputs.html index 5bc8a16f83..5808b384cf 100644 --- a/app/components/task/details/output/task-outputs.html +++ b/app/components/task/details/output/task-outputs.html @@ -1,6 +1,6 @@ -
- Task is currently  {{task.state}}  there is no output +
+ Task is currently  {{task.state}}  there is no output to view yet.
diff --git a/app/components/task/details/output/task-outputs.scss b/app/components/task/details/output/task-outputs.scss index 8798621813..7c953d0ddf 100644 --- a/app/components/task/details/output/task-outputs.scss +++ b/app/components/task/details/output/task-outputs.scss @@ -2,29 +2,4 @@ bl-task-outputs { height: 100%; - - > .task-queued { - $loading-overlay-width: 500px; - $loading-overlay-height: 70px; - width: $loading-overlay-width; - height: $loading-overlay-height; - position: absolute; - top: calc(50% - #{$loading-overlay-height/2}); - left: calc(50% - #{$loading-overlay-width/2}); - - display: flex; - align-items: center; - justify-content: center; - font-size: 16px; - - background: $mineshaft-grey; - color: white; - opacity: 0.5; - padding: 10px; - - > .state { - color: map-get($primary, 200); - font-weight: 700; - } - } } diff --git a/app/services/file-service.ts b/app/services/file-service.ts index 436d38380a..16cf4ab0f3 100644 --- a/app/services/file-service.ts +++ b/app/services/file-service.ts @@ -109,7 +109,7 @@ export class FileService extends ServiceBase { public navigateTaskFile(jobId: string, taskId: string, options: NaviagateTaskFileOptions) { return new FileNavigator({ - loadPath: (folder) => this.listFromTask(jobId, taskId, true, { folder }), + loadPath: (folder) => this.listFromTask(jobId, taskId, false, { folder }), getFile: (filename: string) => this.fileFromTask(jobId, taskId, filename), onError: options.onError, }); diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 10a93bebd8..10b57ed477 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -1,5 +1,5 @@ import { List } from "immutable"; -import { BehaviorSubject, Observable } from "rxjs"; +import { AsyncSubject, BehaviorSubject, Observable } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; import { File, ServerError } from "app/models"; @@ -96,7 +96,7 @@ export class FileNavigator { * Load the inital data */ public init() { - this._loadFileInPath(); + this._loadFilesInPath(); } /** @@ -104,16 +104,22 @@ export class FileNavigator { * @param openInNewTab If its the path to a file it will open the file in a new tab */ public navigateTo(path: string, openInNewTab: boolean = true) { + console.log("nav to ", path); if (this._currentPath.value === path) { return; } this._history.push(this._currentPath.value); this._currentPath.next(path); const node = this._tree.value.getNode(path); - if (node.isDirectory) { - if (!this._tree.value.isPathLoaded(path)) { - this._loadFileInPath(path); - } + if (node.isUnkown) { + this._checkIfDirectory(node).subscribe({ + next: (isDir) => { + this._loadPathContent(path, isDir, openInNewTab); + }, + error: (err) => { + console.log("is direrr", err); + }, + }); } else { - this.openFile(path, openInNewTab); + this._loadPathContent(path, node.isDirectory, openInNewTab); } } @@ -124,6 +130,7 @@ export class FileNavigator { public openFile(path: string, openInNewTab: boolean = true) { const openedFiles = this._openedFiles.value; if (!this.isFileOpen(path)) { + console.log("File is not open...."); if (openInNewTab) { openedFiles.push({ path, @@ -172,14 +179,14 @@ export class FileNavigator { } public refresh(path: string = ""): Observable { - return this._loadFileInPath(path); + return this._loadFilesInPath(path); } public loadPath(path: string = ""): Observable { const node = this._tree.value.getNode(path); if (node.isDirectory) { if (!this._tree.value.isPathLoaded(path)) { - return this._loadFileInPath(path); + return this._loadFilesInPath(path); } } return Observable.of(null); @@ -191,7 +198,47 @@ export class FileNavigator { } } - private _loadFileInPath(path: string = null): Observable { + public isDirectory(path: string): Observable { + const node = this._tree.value.getNode(path); + return this._checkIfDirectory(node); + } + + private _checkIfDirectory(node: FileTreeNode): Observable { + if (!node.isUnknown) { return Observable.of(node.isDirectory); } + const proxy = this._loadPath(this._getFolderToLoad(node.path, false)); + const obs = proxy.refresh().flatMap(() => proxy.items.first()).shareReplay(1); + const subject = new AsyncSubject(); + obs.first().subscribe({ + next: (files: List) => { + proxy.dispose(); + if (files.size === 0) { return false; } + console.log("got files", this._getFolderToLoad(node.path, false), files.toJS()); + const file = files.first(); + subject.next(file.isDirectory); + subject.complete(); + }, + error: (e) => (error) => { + proxy.dispose(); + console.log("Got error....", error); + subject.next(false); + subject.complete(); + }, + }); + return subject.asObservable(); + } + + private _loadPathContent(path: string, isDirectory: boolean, openInNewTab: boolean = true) { + console.log("LOad content of", path, isDirectory); + if (isDirectory) { + if (!this._tree.value.isPathLoaded(path)) { + this._loadFilesInPath(path); + } + } else { + this.openFile(path, openInNewTab); + } + } + + private _loadFilesInPath(path: string = null): Observable { this.loadingStatus = LoadingStatus.Loading; if (path === null) { path = this._currentPath.value; } if (!this._proxies[path]) { @@ -223,10 +270,14 @@ export class FileNavigator { return obs; } - private _getFolderToLoad(path: string) { + private _getFolderToLoad(path: string, asDirectory = true) { let fullPath = [this.basePath, path].filter(x => Boolean(x)).join("/"); if (fullPath) { - return CloudPathUtils.asBaseDirectory(fullPath); + if (asDirectory) { + return CloudPathUtils.asBaseDirectory(fullPath); + } else { + return fullPath; + } } return null; } diff --git a/app/services/file/file-navigator/file-tree.model.ts b/app/services/file/file-navigator/file-tree.model.ts index f6ff4b7300..71c14d6830 100644 --- a/app/services/file/file-navigator/file-tree.model.ts +++ b/app/services/file/file-navigator/file-tree.model.ts @@ -3,9 +3,9 @@ import * as path from "path"; import { LoadingStatus } from "app/components/base/loading"; import { File } from "app/models"; +import { FileLoader } from "app/services/file"; import { CloudPathUtils } from "app/utils"; import { fileToTreeNode, generateDir, sortTreeNodes } from "./helper"; -import { FileLoader } from "app/services/file"; export interface FileTreeNodeParams { path: string; @@ -14,6 +14,7 @@ export interface FileTreeNodeParams { loadingStatus?: LoadingStatus; contentLength?: number; lastModified?: Date; + isUnknown?: boolean; } export class FileTreeNode { @@ -22,6 +23,7 @@ export class FileTreeNode { public children: Map; public loadingStatus: LoadingStatus; public name: string; + public isUnknown: boolean; /** * Content length if node is a file @@ -36,7 +38,7 @@ export class FileTreeNode { this.loadingStatus = params.loadingStatus || (this.isDirectory ? LoadingStatus.Loading : LoadingStatus.Ready); this.contentLength = params.contentLength; this.lastModified = params.lastModified; - + this.isUnknown = params.isUnknown || false; this.name = this._computeName(); } @@ -115,6 +117,7 @@ export class FileTreeStructure { path: nodePath, loadingStatus: LoadingStatus.Loading, isDirectory: true, + isUnknown: true, }); } } diff --git a/app/styles/partials/loading-indicators.scss b/app/styles/partials/loading-indicators.scss index 62c58039e2..e0cb61b9ad 100644 --- a/app/styles/partials/loading-indicators.scss +++ b/app/styles/partials/loading-indicators.scss @@ -19,3 +19,27 @@ opacity: 0.25; } } + + +.info-overlay { + $loading-overlay-width: 500px; + $loading-overlay-height: 70px; + width: $loading-overlay-width; + position: absolute; + top: calc(50% - #{$loading-overlay-height/2}); + left: calc(50% - #{$loading-overlay-width/2}); + + text-align: center; + vertical-align: middle; + font-size: 16px; + + background: $mineshaft-grey; + color: white; + opacity: 0.5; + padding: 10px; + + > .highlight { + color: map-get($primary, 200); + font-weight: 700; + } +} From 7a725692ea075e1bb2c47e7c6f8b46699a622893 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 11:37:57 -0700 Subject: [PATCH 08/28] Fix close style bugt --- .../file-explorer-tabs/file-explorer-tabs.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 0012713c69..3d678a7a23 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -58,7 +58,7 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { this._updateTabs(); })); this._fileNavigatorSubs.push(this.fileNavigator.currentNode.subscribe((node) => { - if (node.isDirectory) { + if (node.isDirectory && !node.isUnknown) { this.activePath = null; this._lastFolderExplored = node.path; } else { From bbf007e36cc041a7e46f00233ca336aafa2fee65 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 11:43:48 -0700 Subject: [PATCH 09/28] Cleanup --- app/services/file/file-navigator/file-navigator.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 10b57ed477..1175f4544e 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -109,14 +109,12 @@ export class FileNavigator { this._history.push(this._currentPath.value); this._currentPath.next(path); const node = this._tree.value.getNode(path); - if (node.isUnkown) { + if (node.isUnknown) { this._checkIfDirectory(node).subscribe({ next: (isDir) => { this._loadPathContent(path, isDir, openInNewTab); }, - error: (err) => { - console.log("is direrr", err); - }, + error: (err) => null, }); } else { this._loadPathContent(path, node.isDirectory, openInNewTab); @@ -130,7 +128,6 @@ export class FileNavigator { public openFile(path: string, openInNewTab: boolean = true) { const openedFiles = this._openedFiles.value; if (!this.isFileOpen(path)) { - console.log("File is not open...."); if (openInNewTab) { openedFiles.push({ path, @@ -212,23 +209,19 @@ export class FileNavigator { next: (files: List) => { proxy.dispose(); if (files.size === 0) { return false; } - console.log("got files", this._getFolderToLoad(node.path, false), files.toJS()); const file = files.first(); subject.next(file.isDirectory); subject.complete(); }, error: (e) => (error) => { proxy.dispose(); - console.log("Got error....", error); - subject.next(false); - subject.complete(); + subject.error(error); }, }); return subject.asObservable(); } private _loadPathContent(path: string, isDirectory: boolean, openInNewTab: boolean = true) { - console.log("LOad content of", path, isDirectory); if (isDirectory) { if (!this._tree.value.isPathLoaded(path)) { this._loadFilesInPath(path); From 9c36a0c592f1ed21f8aa58d50c0ba0325c5844be Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 12:24:25 -0700 Subject: [PATCH 10/28] wip --- .../file-explorer-tabs/file-explorer-tabs.component.ts | 10 +++++++++- .../file-explorer-tabs/file-explorer-tabs.html | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 3d678a7a23..8b576d2196 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -40,6 +40,9 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { public closeTab(event: MouseEvent, tab: Tab) { event.stopPropagation(); + const newIndex = tab.Index - 1; + const newTab = newIndex === -1 ? null : this.tabs[newIndex]; + this.activateTab(newTab); this.fileNavigator.closeFile(tab.filename); } @@ -51,6 +54,10 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { } } + public trackByFn(index, tab) { + return tab.filename; + } + private _updateFileNavigatorEvents() { this._lastFolderExplored = ""; this._fileNavigatorSubs.push(this.fileNavigator._openedFiles.subscribe((files) => { @@ -72,8 +79,9 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { } private _updateTabs() { - this.tabs = this.openedFiles.map((file) => { + this.tabs = this.openedFiles.map((file, index) => { return { + index, filename: file.path, displayName: path.basename(file.path), }; diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index 707d88f959..8717ae545c 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -2,7 +2,7 @@
-
{{tab.displayName}} From b898a1b8fbd11ea97f04389a8fda742457783f52 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 14:26:46 -0700 Subject: [PATCH 11/28] Fix --- .../file-explorer-tabs.component.ts | 5 +++-- .../file-explorer-tabs/file-explorer-tabs.html | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 8b576d2196..8f42c10254 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -5,6 +5,7 @@ import { Subscription } from "rxjs/Rx"; import "./file-explorer-tabs.scss"; interface Tab { + index: number; filename: string; displayName: string; } @@ -32,7 +33,7 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { this._clearFileNavigatorSub(); } - public handleMouseDown(event: MouseEvent, tab) { + public handleMouseUp(event: MouseEvent, tab) { if (event.which === 2) { // Middle click this.closeTab(event, tab); } @@ -40,7 +41,7 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { public closeTab(event: MouseEvent, tab: Tab) { event.stopPropagation(); - const newIndex = tab.Index - 1; + const newIndex = tab.index - 1; const newTab = newIndex === -1 ? null : this.tabs[newIndex]; this.activateTab(newTab); this.fileNavigator.closeFile(tab.filename); diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index 8717ae545c..a711983efd 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -2,9 +2,14 @@
-
+
+ {{tab.displayName}} - +
From cd7760f8dadf2a951ffd1d12846ab53a37f49e07 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 14:46:01 -0700 Subject: [PATCH 12/28] Fix middle click scrolling --- .../file-explorer-tabs.component.ts | 13 +++++++++++-- app/services/file/file-navigator/file-navigator.ts | 1 - app/utils/constants.ts | 6 ++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 8f42c10254..f9455d1301 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -1,5 +1,6 @@ -import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; +import { Component, HostListener, Input, OnChanges, OnDestroy } from "@angular/core"; import { FileNavigator, OpenedFile } from "app/services/file"; +import { Constants } from "app/utils"; import * as path from "path"; import { Subscription } from "rxjs/Rx"; import "./file-explorer-tabs.scss"; @@ -33,8 +34,16 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { this._clearFileNavigatorSub(); } + @HostListener("mousedown", ["$event"]) + public preventMiddleClickScrolling(event) { + if (event.button !== Constants.MouseButton.middle) { return; } + + event.preventDefault(); + event.stopPropagation(); + } + public handleMouseUp(event: MouseEvent, tab) { - if (event.which === 2) { // Middle click + if (event.button === Constants.MouseButton.middle) { // Middle click this.closeTab(event, tab); } } diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 1175f4544e..5ef45657fe 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -104,7 +104,6 @@ export class FileNavigator { * @param openInNewTab If its the path to a file it will open the file in a new tab */ public navigateTo(path: string, openInNewTab: boolean = true) { - console.log("nav to ", path); if (this._currentPath.value === path) { return; } this._history.push(this._currentPath.value); this._currentPath.next(path); diff --git a/app/utils/constants.ts b/app/utils/constants.ts index d32908407f..a21d444b16 100644 --- a/app/utils/constants.ts +++ b/app/utils/constants.ts @@ -169,3 +169,9 @@ export const LowPriDiscount = { }; export const Client = (remote.getCurrentWindow() as any).clientConstants; + +export enum MouseButton { + left = 0, + middle = 1, + right = 2, +} From 3105ecf22cd1b0ca4a04a33063a0730e747e4863 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 15:00:25 -0700 Subject: [PATCH 13/28] context menu --- .../file-explorer-tabs.component.ts | 41 +++++++++++++++++-- .../file-explorer-tabs.html | 2 +- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index f9455d1301..41513d185b 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -1,5 +1,6 @@ import { Component, HostListener, Input, OnChanges, OnDestroy } from "@angular/core"; -import { FileNavigator, OpenedFile } from "app/services/file"; +import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; +import { FileLoader, FileNavigator, OpenedFile } from "app/services/file"; import { Constants } from "app/utils"; import * as path from "path"; import { Subscription } from "rxjs/Rx"; @@ -9,6 +10,7 @@ interface Tab { index: number; filename: string; displayName: string; + fileLoader: FileLoader; } @Component({ @@ -24,6 +26,8 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { private _fileNavigatorSubs: Subscription[] = []; private _lastFolderExplored: string = ""; + constructor(private contextMenuService: ContextMenuService) { } + public ngOnChanges(changes) { if (changes.fileNavigator) { this._updateFileNavigatorEvents(); @@ -44,18 +48,38 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { public handleMouseUp(event: MouseEvent, tab) { if (event.button === Constants.MouseButton.middle) { // Middle click - this.closeTab(event, tab); + this.closeTab(tab, event); + } else if (event.button === Constants.MouseButton.right) { + this.showContextMenu(tab); } } - public closeTab(event: MouseEvent, tab: Tab) { - event.stopPropagation(); + public closeTab(tab: Tab, event?: MouseEvent) { + if (event) { + event.stopPropagation(); + } const newIndex = tab.index - 1; const newTab = newIndex === -1 ? null : this.tabs[newIndex]; this.activateTab(newTab); this.fileNavigator.closeFile(tab.filename); } + public closeOtherTabs(tab: Tab) { + this.activateTab(tab); + for (const openTab of this.tabs) { + if (openTab.filename !== tab.filename) { + this.fileNavigator.closeFile(openTab.filename); + } + } + } + + public closeAllTabs() { + this.activateTab(null); + for (const openTab of this.tabs) { + this.fileNavigator.closeFile(openTab.filename); + } + } + public activateTab(tab: Tab) { if (tab) { this.fileNavigator.navigateTo(tab.filename); @@ -64,6 +88,14 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { } } + public showContextMenu(tab: Tab) { + this.contextMenuService.openMenu(new ContextMenu([ + new ContextMenuItem("Close", () => this.closeTab(tab)), + new ContextMenuItem("Close others", () => this.closeOtherTabs(tab)), + new ContextMenuItem("Close All", () => this.closeAllTabs()), + ])); + } + public trackByFn(index, tab) { return tab.filename; } @@ -94,6 +126,7 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { index, filename: file.path, displayName: path.basename(file.path), + fileLoader: file.fileLoader, }; }); } diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index a711983efd..c78e4c445c 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -10,6 +10,6 @@ (mouseup)="handleMouseUp($event, tab)"> {{tab.displayName}} - +
From 300c9d544a38a47f2f15c7ac0c65821b8f765195 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 15:09:47 -0700 Subject: [PATCH 14/28] Reneable load all for task --- app/services/file-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/file-service.ts b/app/services/file-service.ts index 16cf4ab0f3..436d38380a 100644 --- a/app/services/file-service.ts +++ b/app/services/file-service.ts @@ -109,7 +109,7 @@ export class FileService extends ServiceBase { public navigateTaskFile(jobId: string, taskId: string, options: NaviagateTaskFileOptions) { return new FileNavigator({ - loadPath: (folder) => this.listFromTask(jobId, taskId, false, { folder }), + loadPath: (folder) => this.listFromTask(jobId, taskId, true, { folder }), getFile: (filename: string) => this.fileFromTask(jobId, taskId, filename), onError: options.onError, }); From 67924140649b2a67bb619ffa8e30fc7847661eb4 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 15:34:31 -0700 Subject: [PATCH 15/28] Access tabs with keyboard --- .../file-explorer-tabs/file-explorer-tabs.html | 12 ++++++------ .../file-explorer-tabs/file-explorer-tabs.scss | 13 ++++++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html index c78e4c445c..a52d83a5db 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.html @@ -1,15 +1,15 @@
-
+ -
-
+ {{tab.displayName}} - -
+ +
diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss index 19bda7d3c7..82bb9ea771 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -42,11 +42,18 @@ bl-file-explorer-tabs { color: $mineshaft-grey; } } + + + &:focus > .close-icon { + opacity: 1; + } } - > .tab.active .tab-close > .close-icon, - > .tab:hover .tab-close > .close-icon { - opacity: 1; + > .tab.active, > .tab:hover { + .tab-close > .close-icon { + opacity: 1; + } } + } } From 0749c87a23d6a4d278fc92ad9c178b1dcf5b0443 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Fri, 15 Sep 2017 15:53:11 -0700 Subject: [PATCH 16/28] Don't focus on click --- .../file-explorer-tabs.component.ts | 13 +++++++++---- .../file-explorer-tabs/file-explorer-tabs.scss | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index 41513d185b..b7975001b8 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -39,11 +39,16 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { } @HostListener("mousedown", ["$event"]) - public preventMiddleClickScrolling(event) { - if (event.button !== Constants.MouseButton.middle) { return; } + public disableClickDefaultActions(event) { + if (event.button === Constants.MouseButton.left) { + event.preventDefault(); // Prevent focus on left click + } + if (event.button === Constants.MouseButton.middle) { + event.preventDefault(); // Prevent scrolling on middle click + event.stopPropagation(); + return; + } - event.preventDefault(); - event.stopPropagation(); } public handleMouseUp(event: MouseEvent, tab) { diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss index 82bb9ea771..fb1550f5dd 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -19,6 +19,7 @@ bl-file-explorer-tabs { padding-left: 10px; color: $dove-grey; border-right: 1px solid $border-color; + user-select: none; } > .tab.main { From 8f098707aec59598702868bc0b74d1916b9d49c5 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Sun, 17 Sep 2017 20:45:43 -0700 Subject: [PATCH 17/28] navigator --- .../file-explorer/file-explorer-workspace.ts | 116 ++++++++++++++++++ .../file/file-navigator/file-navigator.ts | 4 + 2 files changed, 120 insertions(+) create mode 100644 app/components/file/browse/file-explorer/file-explorer-workspace.ts diff --git a/app/components/file/browse/file-explorer/file-explorer-workspace.ts b/app/components/file/browse/file-explorer/file-explorer-workspace.ts new file mode 100644 index 0000000000..a0833f116b --- /dev/null +++ b/app/components/file/browse/file-explorer/file-explorer-workspace.ts @@ -0,0 +1,116 @@ +import { FileLoader, FileNavigator, FileTreeNode } from "app/services/file"; +import { BehaviorSubject, Observable } from "rxjs"; + +export interface FileSource { + name?: string; + navigator: FileNavigator; + openedFiles?: string[]; +} + +export interface OpenedFile { + path: string; + source: FileSource; + loader: FileLoader; +} +function sourcesFrom(data: FileNavigator | FileSource | FileSource[]): FileSource[] { + if (data instanceof FileNavigator) { + return [{ + name: "Files", + navigator: data, + }]; + } else if (Array.isArray(data)) { + return data; + } else { + return [data]; + } +} + +export class FileExporerWorkspace { + public sources: FileSource[]; + public openedFiles: Observable; + // public openedFolder: Observable; + public currentSource: Observable; + public currentNode: Observable; + + private _currentSource = new BehaviorSubject(null); + private _currentPath = new BehaviorSubject(""); + private _openedFiles = new BehaviorSubject([]); + // private _openedFolder = new BehaviorSubject(null); + + constructor(data: FileNavigator | FileSource | FileSource[]) { + this.sources = sourcesFrom(data); + this.openedFiles = this._openedFiles.asObservable(); + // this.openedFolder = this._openedFolder.asObservable(); + this.currentSource = this._currentSource.asObservable(); + this._currentSource.next(this.sources.first()); + + this.currentNode = this._currentSource.flatMap((source) => { + return Observable.combineLatest(this._currentPath, source.navigator.tree); + }).map(([path, tree]) => { + return tree.getNode(path).clone(); + }).shareReplay(1); + } + + public navigateTo(path: string, source?: FileSource) { + source = this._getSource(source); + if (this._currentPath.value === path && this._currentSource.value === source) { return; } + this._currentPath.next(path); + this._currentSource.next(source); + source.navigator.navigateTo(path); + } + + public isFileOpen(path: string): boolean { + return Boolean(this._openedFiles.value.find(x => x.path === path)); + } + + public openFile(path: string, source?: FileSource) { + source = this._getSource(source); + const openedFiles = this._openedFiles.value; + if (!this.isFileOpen(path)) { + openedFiles.push({ + source: source, + path, + loader: source.navigator.getFile(path), + }); + } + this._openedFiles.next(openedFiles); + } + + /** + * Triggered when a tab select to open a file + * @param filename File to open + * + * If filename is null or undefined it will show the file table viewer at the last position + */ + public openFiles(paths: string[], source?: FileSource) { + source = this._getSource(source); + + const openedFiles = this._openedFiles.value; + for (let path of paths) { + if (!this.isFileOpen(path)) { + openedFiles.push({ + source: source, + path, + loader: source.navigator.getFile(path), + }); + } + } + this._openedFiles.next(openedFiles); + } + + public closeFile(path: string, source?: FileSource) { + source = this._getSource(source); + + const newOpenedFiles = this._openedFiles.value.filter(x => x.path !== path && x.source === source); + this._openedFiles.next(newOpenedFiles); + } + + private _getSource(source?: FileSource) { + if (source) { return source; } + if (this.sources.length === 1) { + return this.sources.first(); + } else { + throw Error("You must specify a source(FileNavigator) when using multi-source"); + } + } +} diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 5ef45657fe..58f601df2e 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -120,6 +120,10 @@ export class FileNavigator { } } + public getFile(path: string): FileLoader { + return this._getFileLoader(path); + } + public isFileOpen(path: string): boolean { return Boolean(this._openedFiles.value.find(x => x.path === path)); } From c6164e8f655d889f45de8dfa39b869aa00ae5837 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 18 Sep 2017 10:26:11 -0700 Subject: [PATCH 18/28] refactor --- .../blob-files-browser.html | 2 +- .../file-explorer-tabs.component.ts | 42 +++--- .../file-explorer/file-explorer-workspace.ts | 65 +++++++-- .../file-explorer/file-explorer.component.ts | 65 +++++---- .../browse/file-explorer/file-explorer.html | 28 ++-- .../file-tree-view.component.ts | 12 +- .../file-tree-view/file-tree-view.html | 2 +- .../file/browse/file-explorer/index.ts | 1 + .../file/browse/node-file-browse.html | 2 +- .../details/output/task-outputs.component.ts | 22 ++- .../task/details/output/task-outputs.html | 2 +- .../file/file-navigator/file-navigator.ts | 135 +++++------------- .../file/file-navigator/file-tree.model.ts | 6 - 13 files changed, 183 insertions(+), 201 deletions(-) diff --git a/app/components/file/browse/blob-files-browser/blob-files-browser.html b/app/components/file/browse/blob-files-browser/blob-files-browser.html index 9636cc6392..5ae082f65c 100644 --- a/app/components/file/browse/blob-files-browser/blob-files-browser.html +++ b/app/components/file/browse/blob-files-browser/blob-files-browser.html @@ -1,3 +1,3 @@ - diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts index b7975001b8..484cca0bfa 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.component.ts @@ -1,6 +1,7 @@ import { Component, HostListener, Input, OnChanges, OnDestroy } from "@angular/core"; import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; -import { FileLoader, FileNavigator, OpenedFile } from "app/services/file"; +import { FileExplorerWorkspace, FileSource, OpenedFile } from "app/components/file/browse/file-explorer"; +import { FileLoader } from "app/services/file"; import { Constants } from "app/utils"; import * as path from "path"; import { Subscription } from "rxjs/Rx"; @@ -11,6 +12,7 @@ interface Tab { filename: string; displayName: string; fileLoader: FileLoader; + source: FileSource; } @Component({ @@ -18,19 +20,19 @@ interface Tab { templateUrl: "file-explorer-tabs.html", }) export class FileExplorerTabsComponent implements OnChanges, OnDestroy { - @Input() public fileNavigator: FileNavigator; + @Input() public workspace: FileExplorerWorkspace; public openedFiles: OpenedFile[] = []; public tabs: Tab[] = []; public activePath: string = null; - private _fileNavigatorSubs: Subscription[] = []; - private _lastFolderExplored: string = ""; + private _workspaceSubs: Subscription[] = []; + private _lastFolderExplored: any = null; constructor(private contextMenuService: ContextMenuService) { } public ngOnChanges(changes) { - if (changes.fileNavigator) { - this._updateFileNavigatorEvents(); + if (changes.workspace) { + this._updateWorkspaceEvents(); } } @@ -66,30 +68,31 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { const newIndex = tab.index - 1; const newTab = newIndex === -1 ? null : this.tabs[newIndex]; this.activateTab(newTab); - this.fileNavigator.closeFile(tab.filename); + this.workspace.closeFile(tab.filename, tab.source); } public closeOtherTabs(tab: Tab) { this.activateTab(tab); for (const openTab of this.tabs) { if (openTab.filename !== tab.filename) { - this.fileNavigator.closeFile(openTab.filename); + this.workspace.closeFile(openTab.filename, tab.source); } } } public closeAllTabs() { this.activateTab(null); - for (const openTab of this.tabs) { - this.fileNavigator.closeFile(openTab.filename); + for (const openedTab of this.tabs) { + this.workspace.closeFile(openedTab.filename, openedTab.source); } } public activateTab(tab: Tab) { if (tab) { - this.fileNavigator.navigateTo(tab.filename); + this.workspace.navigateTo(tab.filename, tab.source); } else { - this.fileNavigator.navigateTo(this._lastFolderExplored); + // Todo change default + this.workspace.navigateTo(this._lastFolderExplored.path, this._lastFolderExplored.source); } } @@ -105,16 +108,16 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { return tab.filename; } - private _updateFileNavigatorEvents() { + private _updateWorkspaceEvents() { this._lastFolderExplored = ""; - this._fileNavigatorSubs.push(this.fileNavigator._openedFiles.subscribe((files) => { + this._workspaceSubs.push(this.workspace.openedFiles.subscribe((files) => { this.openedFiles = files; this._updateTabs(); })); - this._fileNavigatorSubs.push(this.fileNavigator.currentNode.subscribe((node) => { - if (node.isDirectory && !node.isUnknown) { + this._workspaceSubs.push(this.workspace.currentNode.subscribe((node) => { + if (node.treeNode.isDirectory && !node.treeNode.isUnknown) { this.activePath = null; - this._lastFolderExplored = node.path; + this._lastFolderExplored = node; } else { this.activePath = node.path; } @@ -122,7 +125,7 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { } private _clearFileNavigatorSub() { - this._fileNavigatorSubs.forEach(x => x.unsubscribe()); + this._workspaceSubs.forEach(x => x.unsubscribe()); } private _updateTabs() { @@ -131,7 +134,8 @@ export class FileExplorerTabsComponent implements OnChanges, OnDestroy { index, filename: file.path, displayName: path.basename(file.path), - fileLoader: file.fileLoader, + fileLoader: file.loader, + source: file.source, }; }); } diff --git a/app/components/file/browse/file-explorer/file-explorer-workspace.ts b/app/components/file/browse/file-explorer/file-explorer-workspace.ts index a0833f116b..ecb1f1b80b 100644 --- a/app/components/file/browse/file-explorer/file-explorer-workspace.ts +++ b/app/components/file/browse/file-explorer/file-explorer-workspace.ts @@ -1,4 +1,5 @@ import { FileLoader, FileNavigator, FileTreeNode } from "app/services/file"; +import { log } from "app/utils"; import { BehaviorSubject, Observable } from "rxjs"; export interface FileSource { @@ -25,12 +26,18 @@ function sourcesFrom(data: FileNavigator | FileSource | FileSource[]): FileSourc } } -export class FileExporerWorkspace { +export interface CurrentNode { + source: FileSource; + path: string; + treeNode: FileTreeNode; +} + +export class FileExplorerWorkspace { public sources: FileSource[]; public openedFiles: Observable; // public openedFolder: Observable; public currentSource: Observable; - public currentNode: Observable; + public currentNode: Observable; private _currentSource = new BehaviorSubject(null); private _currentPath = new BehaviorSubject(""); @@ -45,28 +52,53 @@ export class FileExporerWorkspace { this._currentSource.next(this.sources.first()); this.currentNode = this._currentSource.flatMap((source) => { - return Observable.combineLatest(this._currentPath, source.navigator.tree); - }).map(([path, tree]) => { - return tree.getNode(path).clone(); + return Observable.combineLatest(this._currentPath, source.navigator.tree).map(([path, tree]) => { + const node = tree.getNode(path); + return { + source, + path: node.path, + treeNode: node, + }; + }); }).shareReplay(1); + + this._openInitalFiles(); } public navigateTo(path: string, source?: FileSource) { source = this._getSource(source); if (this._currentPath.value === path && this._currentSource.value === source) { return; } + this._currentPath.next(path); this._currentSource.next(source); - source.navigator.navigateTo(path); + source.navigator.getNode(path).subscribe((node) => { + if (!node) { return; } + if (node.isDirectory) { + source.navigator.loadPath(path); + } else { + this.openFile(node.path, source); + } + }); } - public isFileOpen(path: string): boolean { - return Boolean(this._openedFiles.value.find(x => x.path === path)); + /** + * Go back up one level + */ + public goBack() { + const path = this._currentPath.value; + if (path === "") { return; } + this.navigateTo(path.split("/").slice(0, -1).join("/"), this._currentSource.value); + } + + public isFileOpen(path: string, source?: FileSource): boolean { + source = this._getSource(source); + return Boolean(this._openedFiles.value.find(x => x.path === path && x.source === source)); } public openFile(path: string, source?: FileSource) { source = this._getSource(source); const openedFiles = this._openedFiles.value; - if (!this.isFileOpen(path)) { + if (!this.isFileOpen(path, source)) { openedFiles.push({ source: source, path, @@ -87,7 +119,7 @@ export class FileExporerWorkspace { const openedFiles = this._openedFiles.value; for (let path of paths) { - if (!this.isFileOpen(path)) { + if (!this.isFileOpen(path, source)) { openedFiles.push({ source: source, path, @@ -105,12 +137,25 @@ export class FileExporerWorkspace { this._openedFiles.next(newOpenedFiles); } + public dispose() { + this.sources.forEach(x => x.navigator.dispose()); + } + private _getSource(source?: FileSource) { if (source) { return source; } if (this.sources.length === 1) { return this.sources.first(); } else { + log.error("You must specify a source(FileNavigator) when using multi-source"); throw Error("You must specify a source(FileNavigator) when using multi-source"); } } + + private _openInitalFiles() { + for (const source of this.sources) { + if (source.openedFiles) { + this.openFiles(source.openedFiles, source); + } + } + } } diff --git a/app/components/file/browse/file-explorer/file-explorer.component.ts b/app/components/file/browse/file-explorer/file-explorer.component.ts index e6636eb01c..daab4855b1 100644 --- a/app/components/file/browse/file-explorer/file-explorer.component.ts +++ b/app/components/file/browse/file-explorer/file-explorer.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from "@a import { Subscription } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; +import { CurrentNode, FileExplorerWorkspace, FileSource } from "app/components/file/browse/file-explorer"; import { FileNavigator, FileTreeNode } from "app/services/file"; import "./file-explorer.scss"; @@ -56,8 +57,14 @@ const fileExplorerDefaultConfig: FileExplorerConfig = { templateUrl: "file-explorer.html", }) export class FileExplorerComponent implements OnChanges, OnDestroy { - @Input() public fileNavigator: FileNavigator; - @Input() public fileNavigators: FileNavigatorEntry[]; + @Input() public set data(data: FileExplorerWorkspace | FileNavigator) { + if (data instanceof FileExplorerWorkspace) { + this.workspace = data; + } else { + this.workspace = new FileExplorerWorkspace(data); + } + this._updateWorkspaceEvents(); + } @Input() public autoExpand = false; @Input() public activeFile: string; @Input() public set config(config: FileExplorerConfig) { @@ -68,26 +75,26 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { @Output() public dropFiles = new EventEmitter(); public LoadingStatus = LoadingStatus; - public currentNode: FileTreeNode; - public currentFileNavigator: FileNavigator; - public currentFileNavigatorEntry: FileNavigatorEntry; + public currentSource: FileSource; + public currentNode: CurrentNode; + public workspace: FileExplorerWorkspace; - private _currentNodeSub: Subscription; + private _workspaceSubs: Subscription[] = []; private _config: FileExplorerConfig = fileExplorerDefaultConfig; public ngOnChanges(inputs) { - if (inputs.fileNavigator) { - this.fileNavigators = [{ name: "Files", navigator: this.fileNavigator }]; - } - if (inputs.fileNavigator || inputs.fileNavigators) { - this.updateCurrentNavigator(this.fileNavigators.first()); - } + // Todo Remove? } public ngOnDestroy() { - this._clearCurrentNodeSub(); + this._clearWorkspaceSubs(); } + /** + * Triggered when a file/folder is selected in the table view + * It will either navigate to the given item or select it depending on the settings. + * @param node Tree node that got selected + */ public nodeSelected(node: FileTreeNode) { // tslint:disable-next-line:no-bitwise if (node.isDirectory && (this.config.selectable & FileExplorerSelectable.folder)) { @@ -96,38 +103,34 @@ export class FileExplorerComponent implements OnChanges, OnDestroy { } else if (!node.isDirectory && (this.config.selectable & FileExplorerSelectable.file)) { this.activeFileChange.emit(node.path); } else { - this.navigateTo(node.path); + this.navigateTo(node.path, this.currentSource); } } - public navigateTo(path: string) { - this.currentFileNavigator.navigateTo(path); - } - - public updateCurrentNavigator(entry: FileNavigatorEntry) { - this.currentFileNavigatorEntry = entry; - this.currentFileNavigator = entry.navigator; - this._updateNavigatorEvents(); + public navigateTo(path: string, source: FileSource) { + this.workspace.navigateTo(path, source); } public goBack() { - this.currentFileNavigator.goBack(); + this.workspace.goBack(); } public handleDrop(event: FileDropEvent) { this.dropFiles.emit(event); } - private _updateNavigatorEvents() { - this._clearCurrentNodeSub(); - this._currentNodeSub = this.currentFileNavigator.currentNode.subscribe((node) => { + private _updateWorkspaceEvents() { + this._clearWorkspaceSubs(); + this._workspaceSubs.push(this.workspace.currentNode.subscribe((node) => { this.currentNode = node; - }); + })); + this._workspaceSubs.push(this.workspace.currentSource.subscribe((source) => { + this.currentSource = source; + })); } - private _clearCurrentNodeSub() { - if (this._currentNodeSub) { - this._currentNodeSub.unsubscribe(); - } + private _clearWorkspaceSubs() { + this._workspaceSubs.forEach(x => x.unsubscribe()); + this._workspaceSubs = []; } } diff --git a/app/components/file/browse/file-explorer/file-explorer.html b/app/components/file/browse/file-explorer/file-explorer.html index b3d22153e8..92642842bf 100644 --- a/app/components/file/browse/file-explorer/file-explorer.html +++ b/app/components/file/browse/file-explorer/file-explorer.html @@ -1,29 +1,31 @@
-
- - - + + - diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts index 63cbebe5a7..376b3b14ce 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts @@ -20,6 +20,8 @@ export interface TreeRow { }) export class FileTreeViewComponent implements OnChanges, OnDestroy { @Input() public fileNavigator: FileNavigator; + @Input() public currentPath: string; + @Input() public active: boolean = true; @Input() public name: string; @Input() public autoExpand = false; @Input() public canDropExternalFiles = false; @@ -28,7 +30,6 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy { @HostBinding("class.expanded") public expanded = true; - public currentNode: FileTreeNode; public expandedDirs: StringMap = {}; public treeRows: TreeRow[] = []; public refreshing: boolean; @@ -46,13 +47,6 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy { this._tree = tree; this._buildTreeRows(tree); })); - - this._navigatorSubs.push(this.fileNavigator.currentNode.subscribe((node) => { - if (!this.currentNode || node.path !== this.currentNode.path) { - this.expandPath(node.path); - } - this.currentNode = node; - })); } } @@ -65,7 +59,6 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy { this.toggleExpanded(treeRow); } this.navigate.emit(treeRow.path); - this.fileNavigator.navigateTo(treeRow.path); } public handleCaretClick(treeRow: TreeRow, event: MouseEvent) { @@ -109,7 +102,6 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy { public handleClickTreeViewHeader() { this.expanded = true; this.navigate.emit(""); - this.fileNavigator.navigateTo(""); } /** diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html index d40a93dac9..518f89f923 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html @@ -29,7 +29,7 @@ class="tree-row" [style.padding-left]="((treeRow.indent + 1) * 12) + 'px'" [title]="treeRow.path" - [class.active]="treeRow.path === currentNode?.path" + [class.active]="active && treeRow.path === currentPath" [class.drop-target]="treeRow.path.startsWith(dropTargetPath)" (click)="handleClick(treeRow)" (dragenter)="dragEnterRow($event, treeRow)" diff --git a/app/components/file/browse/file-explorer/index.ts b/app/components/file/browse/file-explorer/index.ts index f849ee6e0e..1a5ae2072e 100644 --- a/app/components/file/browse/file-explorer/index.ts +++ b/app/components/file/browse/file-explorer/index.ts @@ -1,4 +1,5 @@ export * from "./file-explorer.component"; export * from "./file-explorer-tabs"; +export * from "./file-explorer-workspace"; export * from "./file-table-view"; export * from "./file-tree-view"; diff --git a/app/components/file/browse/node-file-browse.html b/app/components/file/browse/node-file-browse.html index 50f6012f2c..f239d4ff39 100644 --- a/app/components/file/browse/node-file-browse.html +++ b/app/components/file/browse/node-file-browse.html @@ -1,2 +1,2 @@ - + diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index fbfb1e7e4d..648b472249 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -1,5 +1,5 @@ -import { Component, Input, OnChanges } from "@angular/core"; -import { FileNavigatorEntry } from "app/components/file/browse/file-explorer"; +import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; +import { FileExplorerWorkspace, FileNavigatorEntry } from "app/components/file/browse/file-explorer"; import { ServerError, Task, TaskState } from "app/models"; import { FileService, StorageService } from "app/services"; import { FileLoader } from "app/services/file"; @@ -21,7 +21,7 @@ const outputTabs = [ selector: "bl-task-outputs", templateUrl: "task-outputs.html", }) -export class TaskOutputsComponent implements OnChanges { +export class TaskOutputsComponent implements OnChanges, OnDestroy { @Input() public jobId: string; @Input() public task: Task; @@ -34,6 +34,7 @@ export class TaskOutputsComponent implements OnChanges { public fileNavigators: FileNavigatorEntry[] = []; public isTaskQueued = false; public stateTooltip: string; + public workspace: FileExplorerWorkspace; private _taskOutputContainer: string; @@ -59,6 +60,10 @@ export class TaskOutputsComponent implements OnChanges { } } + public ngOnDestroy() { + this.workspace.dispose(); + } + public selectOutputType(type: OutputType) { this.selectedTab = type; } @@ -74,7 +79,6 @@ export class TaskOutputsComponent implements OnChanges { const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.task.id, { onError: (error) => this._processTaskFilesError(error), }); - nodeNavigator.openFiles(["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"]); nodeNavigator.init(); const taskOutputPrefix = `${this.task.id}/$TaskOutput/`; @@ -89,11 +93,15 @@ export class TaskOutputsComponent implements OnChanges { }); taskLogsNavigator.init(); - this.fileNavigators = [ - { name: "Node files", navigator: nodeNavigator }, + this.workspace = new FileExplorerWorkspace([ + { + name: "Node files", + navigator: nodeNavigator, + openedFiles: ["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"], + }, { name: "Persisted output", navigator: taskOutputNavigator }, { name: "Persisted logs", navigator: taskLogsNavigator }, - ]; + ]); }); } diff --git a/app/components/task/details/output/task-outputs.html b/app/components/task/details/output/task-outputs.html index 5808b384cf..fa7c18c12e 100644 --- a/app/components/task/details/output/task-outputs.html +++ b/app/components/task/details/output/task-outputs.html @@ -1,4 +1,4 @@ - +
Task is currently  {{task.state}}  there is no output diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 58f601df2e..335f006f54 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -6,7 +6,7 @@ import { File, ServerError } from "app/models"; import { RxListProxy } from "app/services/core"; import { FileLoader } from "app/services/file"; import { CloudPathUtils, ObjectUtils } from "app/utils"; -import { FileTreeNode, FileTreeStructure, OpenedFile } from "./file-tree.model"; +import { FileTreeNode, FileTreeStructure } from "./file-tree.model"; export interface FileNavigatorConfig { /** @@ -43,15 +43,6 @@ export interface FileNavigatorConfig { * This can be extended for a node, task or blob file list */ export class FileNavigator { - /** - * Path of the file/directory currently being viewed - */ - public currentPath: Observable; - - /** - * Tree node that is currently being viewed - */ - public currentNode: Observable; public loadingStatus = LoadingStatus.Ready; public basePath: string; public tree: Observable; @@ -68,10 +59,7 @@ export class FileNavigator { public _openedFiles = new BehaviorSubject([]); public error: ServerError; - private _currentPath = new BehaviorSubject(""); private _tree = new BehaviorSubject(null); - - private _history: string[] = []; private _loadPath: (folder: string) => RxListProxy; private _proxies: StringMap> = {}; @@ -83,11 +71,7 @@ export class FileNavigator { this._loadPath = config.loadPath; this._getFileLoader = config.getFile; this._onError = config.onError; - this.currentPath = this._currentPath.asObservable(); this._tree.next(new FileTreeStructure(this.basePath)); - this.currentNode = Observable.combineLatest(this._currentPath, this._tree).map(([path, tree]) => { - return tree.getNode(path).clone(); - }).shareReplay(1); this.tree = this._tree.asObservable(); this.openedFiles = this._openedFiles.asObservable(); } @@ -96,27 +80,34 @@ export class FileNavigator { * Load the inital data */ public init() { - this._loadFilesInPath(); + this._loadFilesInPath(""); } /** * @param path Path of the file/directory to navigate to * @param openInNewTab If its the path to a file it will open the file in a new tab */ - public navigateTo(path: string, openInNewTab: boolean = true) { - if (this._currentPath.value === path) { return; } - this._history.push(this._currentPath.value); - this._currentPath.next(path); + public loadPath(path: string) { + return this.getNode(path).flatMap((node) => { + return this._loadFilesInPath(path); + }).shareReplay(1); + } + + /** + * Get the node at the path. If the node is not in the tree it will list + * @param path Path of the node + * @returns the node if it exsits or null if not + */ + public getNode(path: string): Observable { const node = this._tree.value.getNode(path); if (node.isUnknown) { - this._checkIfDirectory(node).subscribe({ - next: (isDir) => { - this._loadPathContent(path, isDir, openInNewTab); - }, - error: (err) => null, + return this._checkIfDirectory(node).map(() => { + return this._tree.value.getNode(path); + }).catch(() => { + return Observable.of(null); }); } else { - this._loadPathContent(path, node.isDirectory, openInNewTab); + return Observable.of(node); } } @@ -124,72 +115,18 @@ export class FileNavigator { return this._getFileLoader(path); } - public isFileOpen(path: string): boolean { - return Boolean(this._openedFiles.value.find(x => x.path === path)); - } - - public openFile(path: string, openInNewTab: boolean = true) { - const openedFiles = this._openedFiles.value; - if (!this.isFileOpen(path)) { - if (openInNewTab) { - openedFiles.push({ - path, - fileLoader: this._getFileLoader(path), - }); - } - } - this._openedFiles.next(openedFiles); - } - - /** - * Triggered when a tab select to open a file - * @param filename File to open - * - * If filename is null or undefined it will show the file table viewer at the last position - */ - public openFiles(paths: string[]) { - const openedFiles = this._openedFiles.value; - for (let path of paths) { - if (!this.isFileOpen(path)) { - openedFiles.push({ - path, - fileLoader: this._getFileLoader(path), - }); - } - } - this._openedFiles.next(openedFiles); - } - - public closeFile(path: string) { - const newOpenedFiles = this._openedFiles.value.filter(x => x.path !== path); - this._openedFiles.next(newOpenedFiles); - } - - /** - * Go back up one level - */ - public goBack() { - const path = this._currentPath.value; - if (path === "") { return; } - this.navigateTo(path.split("/").slice(0, -1).join("/")); - } - - public list(): Observable> { - return this._proxies[this._currentPath.value].items.first(); - } - public refresh(path: string = ""): Observable { return this._loadFilesInPath(path); } - public loadPath(path: string = ""): Observable { + public loadFilesInPath(path: string = ""): Observable { const node = this._tree.value.getNode(path); if (node.isDirectory) { if (!this._tree.value.isPathLoaded(path)) { return this._loadFilesInPath(path); } } - return Observable.of(null); + return Observable.of(node); } public dispose() { @@ -213,6 +150,7 @@ export class FileNavigator { proxy.dispose(); if (files.size === 0) { return false; } const file = files.first(); + this._tree.value.addFiles(files); subject.next(file.isDirectory); subject.complete(); }, @@ -224,32 +162,24 @@ export class FileNavigator { return subject.asObservable(); } - private _loadPathContent(path: string, isDirectory: boolean, openInNewTab: boolean = true) { - if (isDirectory) { - if (!this._tree.value.isPathLoaded(path)) { - this._loadFilesInPath(path); - } - } else { - this.openFile(path, openInNewTab); - } - } - - private _loadFilesInPath(path: string = null): Observable { + private _loadFilesInPath(path: string): Observable { this.loadingStatus = LoadingStatus.Loading; - if (path === null) { path = this._currentPath.value; } if (!this._proxies[path]) { this._proxies[path] = this._loadPath(this._getFolderToLoad(path)); } const proxy = this._proxies[path]; - const obs = proxy.refresh().flatMap(() => proxy.items.first()).share(); - obs.subscribe({ + const output = new AsyncSubject(); + proxy.refresh().flatMap(() => proxy.items.first()).subscribe({ next: (files: List) => { this.loadingStatus = LoadingStatus.Ready; const tree = this._tree.value; tree.addFiles(files); - tree.getNode(path).markAsLoaded(); + const node = tree.getNode(path); + node.markAsLoaded(); this._tree.next(tree); + output.next(node); + output.complete(); }, error: (error) => { if (this._onError) { @@ -258,12 +188,15 @@ export class FileNavigator { } this.error = error; const tree = this._tree.value; - tree.getNode(path).loadingStatus = LoadingStatus.Error; + const node = tree.getNode(path); + node.loadingStatus = LoadingStatus.Error; this._tree.next(tree); + output.next(node); + output.complete(); }, }); - return obs; + return output; } private _getFolderToLoad(path: string, asDirectory = true) { diff --git a/app/services/file/file-navigator/file-tree.model.ts b/app/services/file/file-navigator/file-tree.model.ts index 71c14d6830..1253e49299 100644 --- a/app/services/file/file-navigator/file-tree.model.ts +++ b/app/services/file/file-navigator/file-tree.model.ts @@ -3,7 +3,6 @@ import * as path from "path"; import { LoadingStatus } from "app/components/base/loading"; import { File } from "app/models"; -import { FileLoader } from "app/services/file"; import { CloudPathUtils } from "app/utils"; import { fileToTreeNode, generateDir, sortTreeNodes } from "./helper"; @@ -154,8 +153,3 @@ export class FileTreeStructure { } } } - -export interface OpenedFile { - path: string; // Fullpath - fileLoader: FileLoader; -} From 3ec5540d4dcda0ef81b94832e488ecd98b564282 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 18 Sep 2017 10:34:38 -0700 Subject: [PATCH 19/28] Fix loading folder --- .../code-file-viewer/code-file-viewer.component.ts | 3 +-- app/services/file/file-navigator/file-navigator.ts | 10 ++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/components/file/details/code-file-viewer/code-file-viewer.component.ts b/app/components/file/details/code-file-viewer/code-file-viewer.component.ts index 90a0f5ef7a..4cf287c021 100644 --- a/app/components/file/details/code-file-viewer/code-file-viewer.component.ts +++ b/app/components/file/details/code-file-viewer/code-file-viewer.component.ts @@ -1,9 +1,8 @@ import { Component, Input, OnChanges } from "@angular/core"; import { LoadingStatus } from "app/components/base/loading"; -import { File, ServerError } from "app/models"; +import { File } from "app/models"; import { FileLoader } from "app/services/file"; -import { Constants } from "app/utils"; import "./code-file-viewer.scss"; const maxSize = 100000; // 100KB diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 335f006f54..982e797815 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -52,11 +52,6 @@ export class FileNavigator { */ public currentFileLoader: FileLoader; - /** - * List of files opended for a quick switch - */ - public openedFiles: Observable; - public _openedFiles = new BehaviorSubject([]); public error: ServerError; private _tree = new BehaviorSubject(null); @@ -73,7 +68,6 @@ export class FileNavigator { this._onError = config.onError; this._tree.next(new FileTreeStructure(this.basePath)); this.tree = this._tree.asObservable(); - this.openedFiles = this._openedFiles.asObservable(); } /** @@ -88,7 +82,7 @@ export class FileNavigator { * @param openInNewTab If its the path to a file it will open the file in a new tab */ public loadPath(path: string) { - return this.getNode(path).flatMap((node) => { + return this.getNode(path).cascade((node) => { return this._loadFilesInPath(path); }).shareReplay(1); } @@ -169,7 +163,7 @@ export class FileNavigator { } const proxy = this._proxies[path]; const output = new AsyncSubject(); - proxy.refresh().flatMap(() => proxy.items.first()).subscribe({ + proxy.refresh().flatMap(() => proxy.items.first()).share().subscribe({ next: (files: List) => { this.loadingStatus = LoadingStatus.Ready; From 48f8033018d1953eeccc07f91fa644af3df699f3 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 18 Sep 2017 10:36:11 -0700 Subject: [PATCH 20/28] Remove --- app/components/task/details/output/index.ts | 1 - .../task/details/output/task-log.component.ts | 207 ------------------ .../task/details/output/task-log.html | 25 --- .../task/details/task-details.module.ts | 4 +- 4 files changed, 2 insertions(+), 235 deletions(-) delete mode 100644 app/components/task/details/output/task-log.component.ts delete mode 100644 app/components/task/details/output/task-log.html diff --git a/app/components/task/details/output/index.ts b/app/components/task/details/output/index.ts index e2d19e8c5a..edc5792527 100644 --- a/app/components/task/details/output/index.ts +++ b/app/components/task/details/output/index.ts @@ -1,2 +1 @@ -export * from "./task-log.component"; export * from "./task-outputs.component"; diff --git a/app/components/task/details/output/task-log.component.ts b/app/components/task/details/output/task-log.component.ts deleted file mode 100644 index c186d8175e..0000000000 --- a/app/components/task/details/output/task-log.component.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { FormControl } from "@angular/forms"; -import { BehaviorSubject, Observable, Subscription } from "rxjs"; - -import { File, ServerError, Task, TaskState } from "app/models"; -import { PollObservable } from "app/services/core"; -import { FileLoader } from "app/services/file"; -import { FileService } from "app/services/file-service"; -import { prettyBytes } from "app/utils"; - -const defaultOutputFileNames = ["stdout.txt", "stderr.txt"]; - -@Component({ - selector: "bl-task-log", - templateUrl: "task-log.html", -}) -export class TaskLogComponent implements OnInit, OnChanges, OnDestroy { - @Input() - public jobId: string; - - @Input() - public task: Task; - - @ViewChild("addfileInput") - public addfileInput: ElementRef; - - public outputFileNames = defaultOutputFileNames.slice(); - public selectedOutputFile: "stdout.txt" | "stderr.txt" = defaultOutputFileNames[0] as any; - public fileSizes: { [filename: string]: string } = {}; - public filterControl = new FormControl(); - public filteredOptions: Observable; - public addingFile = false; - public fileLoaderMap: StringMap = {}; - - private _taskFileSubscription: Subscription; - private _initialQueryOptions = { maxItems: 500 }; - private _options: BehaviorSubject; - private _currentTaskId: string = null; - private _poller: PollObservable; - private _refreshInterval: number = 5000; - private _fileMap = {}; - - constructor(private fileService: FileService) { - this._options = new BehaviorSubject([]); - this.filteredOptions = this._options.asObservable(); - } - - public ngOnInit() { - this.filteredOptions = this.filterControl.valueChanges - .map((nameFilter) => { - return nameFilter - ? this._options.value.filter(option => new RegExp(`${nameFilter}`, "gi").test(option)) - : this._options.value; - }); - } - - public ngOnChanges(inputs) { - if (inputs.jobId || inputs.task) { - /** - * ngOnChanges is fired multiple times for the same task selection - * so this should cut down on chatter. - */ - if (this.task && this._currentTaskId !== this.task.id) { - this._currentTaskId = this.task.id; - this.fileSizes = {}; - this.addingFile = false; - - this._loadTaskFilesData(); - this._updateFileData(); - } - } - } - - /** - * Navigating away from the job and task so reset the tabs - */ - public ngOnDestroy() { - this.resetTabs(); - this._clearTaskFilesSubscription(); - this._clearFileSizeProxyMap(); - if (this._poller) { - this._poller.destroy(); - } - } - - /** - * Enable/disable the filter control - */ - public toggleFilter() { - this.addingFile = !this.addingFile; - this.filterControl.setValue(null); - if (this.addingFile) { - setTimeout(() => { - this.addfileInput.nativeElement.focus(); - }); - } - } - - /** - * Fires when an item is selected from the autocomplete option list - * @param event - the selection event - * @param item - selected item, add this to a tab if it doesn't already exist - */ - public optionSelected($event: any, item: string) { - if (item && !this.outputFileNames.find(existing => existing === item)) { - this.outputFileNames.push(item); - this.selectedOutputFile = item as any; - this.toggleFilter(); - this._updateFileData(); - } - } - - public resetTabs() { - this.addingFile = false; - this.outputFileNames = defaultOutputFileNames.slice(); - this.selectedOutputFile = defaultOutputFileNames[0] as any; - } - - /** - * Get the task files from the node so we can use them to populate - * the autocomplete control. - */ - private _loadTaskFilesData() { - this._clearTaskFilesSubscription(); - const taskFileData = this.fileService.listFromTask( - this.jobId, - this.task.id, - true, - this._initialQueryOptions, - (error: ServerError) => { - // todo: should i ignore all errors for this call? - return false; - }, - ); - - // poll for files if the job has not completed - if (this.task.state !== TaskState.completed) { - this._poller = taskFileData.startPoll(this._refreshInterval); - } - - this._taskFileSubscription = taskFileData.items.subscribe((items) => { - items.map((file: File) => { - if (this._canAddFileToMap(file)) { - this._fileMap[file.name] = {}; - } - }); - - this._options.next(Object.keys(this._fileMap)); - }); - - taskFileData.fetchNext(true); - } - - /** - * Get the sizes for the output file name collection - */ - private _updateFileData() { - this._clearFileSizeProxyMap(); - for (const filename of this.outputFileNames) { - const fileLoader = this.fileService.fileFromTask(this.jobId, this.task.id, filename); - this.fileLoaderMap[filename] = fileLoader; - - if (this._shouldGetFileSize(filename)) { - fileLoader.getProperties().subscribe({ - next: (file: File) => { - if (file) { - const props = file.properties; - this.fileSizes[filename] = prettyBytes(props && props.contentLength); - } - }, - // unexpected are logged in fileLoader.getProperties() - error: (error) => null, - }); - } - } - } - - /** - * Only get the size of this file if either the task hasn't completed, or - * we don't have the current file size for the file. - */ - private _shouldGetFileSize(filename: string) { - return this.task.state !== TaskState.completed || !this.fileSizes[filename]; - } - - /** - * Ignore directories and any file that is either already in the map, or - * is one of the defaults. - */ - private _canAddFileToMap(file: File) { - return !file.isDirectory && - !(file.name in this._fileMap) && - file.name !== defaultOutputFileNames[0] && - file.name !== defaultOutputFileNames[1]; - } - - private _clearFileSizeProxyMap() { - this.fileLoaderMap = {}; - } - - private _clearTaskFilesSubscription() { - this._fileMap = {}; - if (this._taskFileSubscription) { - this._taskFileSubscription.unsubscribe(); - } - } -} diff --git a/app/components/task/details/output/task-log.html b/app/components/task/details/output/task-log.html deleted file mode 100644 index 47a384db69..0000000000 --- a/app/components/task/details/output/task-log.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- {{filename}} ({{fileSizes[filename] || " - B"}}) -
-
- - - -
-
- -
-
- -
-
-
- -
- - - - {{ option }} - - diff --git a/app/components/task/details/task-details.module.ts b/app/components/task/details/task-details.module.ts index 93fad3407e..4895203264 100644 --- a/app/components/task/details/task-details.module.ts +++ b/app/components/task/details/task-details.module.ts @@ -5,7 +5,7 @@ import { FileBrowseModule } from "app/components/file/browse"; import { FileDetailsModule } from "app/components/file/details"; import { TaskBrowseModule } from "app/components/task/browse"; -import { TaskLogComponent, TaskOutputsComponent } from "./output"; +import { TaskOutputsComponent } from "./output"; import { SubTaskDisplayListComponent, SubTaskPropertiesComponent } from "./sub-tasks"; import { TaskConfigurationComponent } from "./task-configuration.component"; import { TaskDefaultComponent } from "./task-default.component"; @@ -18,7 +18,7 @@ import { TaskTimelineComponent, TaskTimelineStateComponent } from "./task-timeli const components = [ SubTaskDisplayListComponent, SubTaskPropertiesComponent, TaskConfigurationComponent, - TaskDefaultComponent, TaskDependenciesComponent, TaskDetailsComponent, TaskLogComponent, TaskOutputsComponent, + TaskDefaultComponent, TaskDependenciesComponent, TaskDetailsComponent, TaskOutputsComponent, TaskResourceFilesComponent, TaskSubTasksTabComponent, TaskTimelineComponent, TaskTimelineStateComponent, TaskErrorDisplayComponent, ]; From b651c90aeb79800150f78c73e0afb0bee5042b81 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 18 Sep 2017 13:10:53 -0700 Subject: [PATCH 21/28] Added task outputs test --- .../details/output/task-outputs.component.ts | 15 +- .../details/output/task-log.component.spec.ts | 199 ------------------ .../output/task-output.component.spec.ts | 96 +++++++++ 3 files changed, 103 insertions(+), 207 deletions(-) delete mode 100644 test/app/components/task/details/output/task-log.component.spec.ts create mode 100644 test/app/components/task/details/output/task-output.component.spec.ts diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index 648b472249..0f970357d5 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -73,7 +73,13 @@ export class TaskOutputsComponent implements OnChanges, OnDestroy { } private _updateNavigator() { + console.log("update nav"); + if (this.isTaskQueued) { + this.workspace = null; + return; + } StorageUtils.getSafeContainerName(this.jobId).then((container) => { + console.log("Container", container); this._taskOutputContainer = container; this._clearFileNavigator(); const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.task.id, { @@ -81,18 +87,12 @@ export class TaskOutputsComponent implements OnChanges, OnDestroy { }); nodeNavigator.init(); - const taskOutputPrefix = `${this.task.id}/$TaskOutput/`; + const taskOutputPrefix = `${this.task.id}/`; const taskOutputNavigator = this.storageService.navigateContainerBlobs(container, taskOutputPrefix, { onError: (error) => this._processBlobError(error), }); taskOutputNavigator.init(); - const taskLogsPrefix = `${this.task.id}/$TaskLog/`; - const taskLogsNavigator = this.storageService.navigateContainerBlobs(container, taskLogsPrefix, { - onError: (error) => this._processBlobError(error), - }); - taskLogsNavigator.init(); - this.workspace = new FileExplorerWorkspace([ { name: "Node files", @@ -100,7 +100,6 @@ export class TaskOutputsComponent implements OnChanges, OnDestroy { openedFiles: ["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"], }, { name: "Persisted output", navigator: taskOutputNavigator }, - { name: "Persisted logs", navigator: taskLogsNavigator }, ]); }); } diff --git a/test/app/components/task/details/output/task-log.component.spec.ts b/test/app/components/task/details/output/task-log.component.spec.ts deleted file mode 100644 index a4480087db..0000000000 --- a/test/app/components/task/details/output/task-log.component.spec.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, TestBed } from "@angular/core/testing"; -import { MdAutocomplete } from "@angular/material"; -import { By } from "@angular/platform-browser"; -import { RouterTestingModule } from "@angular/router/testing"; -import { Observable } from "rxjs"; - -import { TaskLogComponent } from "app/components/task/details/output"; -import { File } from "app/models"; -import { FileService } from "app/services"; -import * as Fixtures from "test/fixture"; -import { RxMockListProxy } from "test/utils/mocks"; - -const fileSizeMap: Map = new Map() - .set("stdout.txt", 10) - .set("stderr.txt", 20) - .set("banana.txt", 30) - .set("apple.txt", 40) - .set("pear.txt", 50); - -describe("TaskLogComponent", () => { - let fixture: ComponentFixture; - let component: TaskLogComponent; - - let anyComponent: any; - let fileListProxy: RxMockListProxy; - let fileServiceSpy: any; - - beforeEach(() => { - fileListProxy = new RxMockListProxy(File, { - cacheKey: "name", - items: [ - Fixtures.file.create({ name: "stdout.txt" }), - Fixtures.file.create({ name: "stderr.txt" }), - Fixtures.file.create({ name: "banana.txt" }), - Fixtures.file.create({ name: "apple.txt" }), - Fixtures.file.create({ name: "pear.txt" }), - ], - }); - - fileServiceSpy = { - listFromTask: (jobid: string, taskId: string, recursive?: boolean, options?: any) => fileListProxy, - - fileFromTask: (jobid: string, taskId: string, filename: string) => { - return { - getProperties: () => Observable.of(Fixtures.file.create({ - name: filename, - properties: { contentLength: fileSizeMap.get(filename) || 100 }, - })), - }; - }, - }; - - TestBed.configureTestingModule({ - imports: [RouterTestingModule], - declarations: [ - MdAutocomplete, TaskLogComponent, - ], - providers: [ - { provide: FileService, useValue: fileServiceSpy }, - ], - schemas: [NO_ERRORS_SCHEMA], - }); - - fixture = TestBed.createComponent(TaskLogComponent); - component = fixture.componentInstance; - component.jobId = "bobs-job-1"; - component.task = Fixtures.task.create({ id: "bobs-task" }); - anyComponent = component as any; - fixture.detectChanges(); - }); - - describe("on initial default load", () => { - it("initial ui state is correct", () => { - expect(component.jobId).toBe("bobs-job-1"); - expect(component.outputFileNames.length).toBe(2); - expect(component.selectedOutputFile).toBe("stdout.txt"); - expect(Object.keys(component.fileSizes).length).toBe(0); - expect(component.filterControl.value).toBeNull(); - expect(component.addingFile).toBeFalsy(); - - expect(anyComponent._currentTaskId).toBeNull(); - expect(anyComponent._refreshInterval).toBe(5000); - }); - - it("shows default tabs", () => { - const container = fixture.debugElement.query(By.css(".file-tabs")); - expect(container.nativeElement.textContent).toContain("stdout.txt ( - B)"); - expect(container.nativeElement.textContent).toContain("stderr.txt ( - B)"); - expect(container).toBeVisible(); - }); - - it("shows default add file tab", () => { - const container = fixture.debugElement.query(By.css(".file-tab-more")); - expect(container).toBeVisible(); - }); - - it("reset tabs button not shown", () => { - const container = fixture.debugElement.query(By.css("div[title=\"Reset to default\"]")); - expect(container).toBeNull(); - }); - }); - - describe("loads file sizes based on current job and task", () => { - beforeEach(() => { - component.ngOnChanges({ jobId: component.jobId, task: component.task }); - fixture.detectChanges(); - }); - - it("current task id set", () => { - expect(anyComponent._currentTaskId).toBe("bobs-task"); - }); - - it("has loaded file sizes", () => { - expect(Object.keys(component.fileSizes).length).toBe(2); - expect(component.fileSizes["stdout.txt"]).toBe("10 B"); - expect(component.fileSizes["stderr.txt"]).toBe("20 B"); - }); - - it("ui shows file sizes", () => { - const container = fixture.debugElement.query(By.css(".file-tabs")); - expect(container.nativeElement.textContent).toContain("stdout.txt (10 B)"); - expect(container.nativeElement.textContent).toContain("stderr.txt (20 B)"); - }); - - it("default file tab is stdout.txt", () => { - const container = fixture.debugElement.query(By.css(".file-tab.active")); - expect(container.nativeElement.textContent).toContain("stdout.txt (10 B)"); - }); - }); - - describe("loads additional task output files", () => { - beforeEach(() => { - component.ngOnChanges({ jobId: component.jobId, task: component.task }); - fixture.detectChanges(); - }); - - it("has 3 extra outputs for auto-complete filter", () => { - expect(anyComponent._options.value.length).toBe(3); - }); - - it("won't add either default file to _options list", () => { - let file = { name: "stdout.txt", isDirectory: false }; - expect(anyComponent._canAddFileToMap(file)).toBe(false); - - file.name = "stderr.txt"; - expect(anyComponent._canAddFileToMap(file)).toBe(false); - - file.isDirectory = true; - expect(anyComponent._canAddFileToMap(file)).toBe(false); - }); - - it("hasn't added new files to tab list", () => { - expect(component.outputFileNames.length).toBe(2); - }); - }); - - describe("can add a new file to a tab", () => { - beforeEach(() => { - component.ngOnChanges({ jobId: component.jobId, task: component.task }); - fixture.detectChanges(); - }); - - it("click add shows filter", () => { - const tabElements = fixture.debugElement.queryAll(By.css(".file-tab-more")); - tabElements[0].nativeElement.click(); - fixture.detectChanges(); - expect(component.addingFile).toBe(true); - }); - - it("can filter options", (done) => { - component.filteredOptions.subscribe((items) => { - expect(items.length).toBe(1); - expect(items[0]).toBe("banana.txt"); - done(); - }); - - component.filterControl.setValue("banana"); - }); - - it("selecting option loads file into tab list and gets file size", () => { - component.optionSelected({}, "banana.txt"); - expect(component.outputFileNames.length).toBe(3); - expect(component.selectedOutputFile).toBe("banana.txt"); - expect(Object.keys(component.fileSizes).length).toBe(3); - expect(component.fileSizes["banana.txt"]).toBe("30 B"); - }); - - it("clicking reset will remove additional tabs", () => { - component.optionSelected({}, "apple.txt"); - expect(component.outputFileNames.length).toBe(3); - expect(component.selectedOutputFile).toBe("apple.txt"); - - component.resetTabs(); - expect(component.outputFileNames.length).toBe(2); - expect(component.selectedOutputFile).toBe("stdout.txt"); - }); - }); -}); diff --git a/test/app/components/task/details/output/task-output.component.spec.ts b/test/app/components/task/details/output/task-output.component.spec.ts new file mode 100644 index 0000000000..b484db37a5 --- /dev/null +++ b/test/app/components/task/details/output/task-output.component.spec.ts @@ -0,0 +1,96 @@ +import { Component, DebugElement, NO_ERRORS_SCHEMA } from "@angular/core"; +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { By } from "@angular/platform-browser"; +import { RouterTestingModule } from "@angular/router/testing"; + +import { fakeAsync, tick } from "@angular/core/testing"; +import { TaskOutputsComponent } from "app/components/task/details/output"; +import { Task, TaskState } from "app/models"; +import { FileService, StorageService } from "app/services"; +import { StorageUtils } from "app/utils"; + +@Component({ + template: ``, +}) +class TestComponent { + public jobId: string = "job-1"; + public task = new Task({ id: "task-0", state: TaskState.active }); +} + +describe("TaskLogComponent", () => { + let fixture: ComponentFixture; + let testComponent: TestComponent; + let component: TaskOutputsComponent; + let de: DebugElement; + let fakeNavigator; + let fileServiceSpy: any; + let storageServiceSpy: any; + + beforeEach(() => { + fakeNavigator = { + init: () => null, + getFile: () => null, + }; + spyOn(StorageUtils, "getSafeContainerName").and.callFake(x => Promise.resolve(x)); + fileServiceSpy = { + navigateTaskFile: jasmine.createSpy("navigateTaskFile").and.returnValue(fakeNavigator), + }; + + storageServiceSpy = { + navigateContainerBlobs: jasmine.createSpy("navigateContainerBlobs").and.returnValue(fakeNavigator), + }; + + TestBed.configureTestingModule({ + imports: [RouterTestingModule], + declarations: [ + TaskOutputsComponent, TestComponent, + ], + providers: [ + { provide: FileService, useValue: fileServiceSpy }, + { provide: StorageService, useValue: storageServiceSpy }, + ], + schemas: [NO_ERRORS_SCHEMA], + }); + + fixture = TestBed.createComponent(TestComponent); + de = fixture.debugElement; + testComponent = fixture.componentInstance; + component = de.query(By.css("bl-task-outputs")).componentInstance; + fixture.detectChanges(); + }); + + it("when task is active it should not start loading anything", () => { + testComponent.task = new Task({ id: "task-1", state: TaskState.active }); + fixture.detectChanges(); + expect(component.isTaskQueued).toBe(true); + expect(de.query(By.css("bl-file-explorer"))).toBeFalsy(); + expect(de.query(By.css(".task-queued"))).not.toBeFalsy(); + expect(fileServiceSpy.navigateTaskFile).not.toHaveBeenCalled(); + expect(storageServiceSpy.navigateContainerBlobs).not.toHaveBeenCalled(); + }); + + it("when task is preparing it should not start loading anything", () => { + testComponent.task = new Task({ id: "task-1", state: TaskState.preparing }); + fixture.detectChanges(); + expect(component.isTaskQueued).toBe(true); + expect(de.query(By.css("bl-file-explorer"))).toBeFalsy(); + expect(de.query(By.css(".task-queued"))).not.toBeFalsy(); + expect(fileServiceSpy.navigateTaskFile).not.toHaveBeenCalled(); + expect(storageServiceSpy.navigateContainerBlobs).not.toHaveBeenCalled(); + }); + + it("when task is running it should show the navigation", fakeAsync(() => { + testComponent.task = new Task({ id: "task-1", state: TaskState.running }); + fixture.detectChanges(); + tick(); + fixture.detectChanges(); + expect(component.isTaskQueued).toBe(false); + expect(de.query(By.css("bl-file-explorer"))).not.toBeFalsy(); + expect(de.query(By.css(".task-queued"))).toBeFalsy(); + + expect(fileServiceSpy.navigateTaskFile).toHaveBeenCalledOnce(); + expect(fileServiceSpy.navigateTaskFile).toHaveBeenCalledWith("job-1", "task-1", jasmine.anything()); + expect(storageServiceSpy.navigateContainerBlobs).toHaveBeenCalledOnce(); + expect(storageServiceSpy.navigateContainerBlobs).toHaveBeenCalledWith("job-1", "task-1/", jasmine.anything()); + })); +}); From 3aabc2d7ca934bfa4cfea0d74188548c9ea558b0 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 18 Sep 2017 13:14:19 -0700 Subject: [PATCH 22/28] remove console.log --- app/components/task/details/output/task-outputs.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index 0f970357d5..1d58f2a1cc 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -73,13 +73,11 @@ export class TaskOutputsComponent implements OnChanges, OnDestroy { } private _updateNavigator() { - console.log("update nav"); if (this.isTaskQueued) { this.workspace = null; return; } StorageUtils.getSafeContainerName(this.jobId).then((container) => { - console.log("Container", container); this._taskOutputContainer = container; this._clearFileNavigator(); const nodeNavigator = this.fileService.navigateTaskFile(this.jobId, this.task.id, { From f43fa22b82726cf01a81f9741c7fa238b1659503 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 14:45:11 -0700 Subject: [PATCH 23/28] Tweaks --- .../file/browse/file-explorer/file-explorer-tabs/index.ts | 2 +- app/services/file/file-navigator/file-navigator.ts | 8 +------- .../task/details/output/task-output.component.spec.ts | 3 +-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts b/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts index d1921e96c2..fa182818f0 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/index.ts @@ -1 +1 @@ -export * from "./file-explorer-tabs.component" +export * from "./file-explorer-tabs.component"; diff --git a/app/services/file/file-navigator/file-navigator.ts b/app/services/file/file-navigator/file-navigator.ts index 1a0aedf545..1fa7b11da2 100644 --- a/app/services/file/file-navigator/file-navigator.ts +++ b/app/services/file/file-navigator/file-navigator.ts @@ -47,11 +47,6 @@ export class FileNavigator { public basePath: string; public tree: Observable; - /** - * Current file loader to display - */ - public currentFileLoader: FileLoader; - public error: ServerError; private _tree = new BehaviorSubject(null); @@ -107,8 +102,7 @@ export class FileNavigator { public getFile(path: string): FileLoader { const loader = this._getFileLoader(CloudPathUtils.join(this.basePath, path)); - this.currentFileLoader.basePath = this.basePath; - + loader.basePath = this.basePath; return loader; } diff --git a/test/app/components/task/details/output/task-output.component.spec.ts b/test/app/components/task/details/output/task-output.component.spec.ts index b484db37a5..dc3ef563f7 100644 --- a/test/app/components/task/details/output/task-output.component.spec.ts +++ b/test/app/components/task/details/output/task-output.component.spec.ts @@ -1,9 +1,8 @@ import { Component, DebugElement, NO_ERRORS_SCHEMA } from "@angular/core"; -import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing"; import { By } from "@angular/platform-browser"; import { RouterTestingModule } from "@angular/router/testing"; -import { fakeAsync, tick } from "@angular/core/testing"; import { TaskOutputsComponent } from "app/components/task/details/output"; import { Task, TaskState } from "app/models"; import { FileService, StorageService } from "app/services"; From 47bad04abbb61e42c5b9ba5ba3634fd7b8738807 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 14:50:12 -0700 Subject: [PATCH 24/28] SHow open folder icon --- .../file-explorer/file-tree-view/file-tree-view.html | 5 ++++- .../file-explorer/file-tree-view/file-tree-view.scss | 12 +++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html index bb16fcf3e5..b849d1163e 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html @@ -40,7 +40,10 @@ - + {{treeRow.name}}
diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.scss b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.scss index eaed2e72a4..2040f17d89 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.scss +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.scss @@ -104,12 +104,14 @@ bl-file-tree-view { background: $whitesmoke-darker; } - > .file-icon > .fa-folder { - color: #f8d979; - } + > .file-icon { + > .fa-folder, > .fa-folder-open { + color: #f8d979; + } - > .file-icon > .fa-file { - color: $dove-grey; + > .fa-file { + color: $dove-grey; + } } } } From 90b8ee59287cf48eab6d62c45f17873281af1bea Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 15:22:23 -0700 Subject: [PATCH 25/28] fix cr comments --- app/components/base/editor/editor.component.ts | 8 ++++---- .../file-explorer-tabs/file-explorer-tabs.scss | 3 +-- .../browse/file-explorer/file-explorer-workspace.ts | 12 +++++------- .../file-explorer/file-tree-view/file-tree-view.html | 4 +--- .../file-explorer/file-tree-view/file-tree-view.scss | 6 +++++- .../details/file-details-view/file-details-view.html | 2 +- .../task/details/output/task-outputs.component.ts | 2 +- app/styles/variables.scss | 2 ++ 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app/components/base/editor/editor.component.ts b/app/components/base/editor/editor.component.ts index c48b8236ae..2eb155f02a 100644 --- a/app/components/base/editor/editor.component.ts +++ b/app/components/base/editor/editor.component.ts @@ -50,7 +50,7 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC public placeholder: string; private _value = ""; private _sub: Subscription; - private _erd: any; + private _resizeDetector: any; get value() { return this._value; } @@ -68,11 +68,11 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC } } public ngAfterViewInit() { - this._erd = elementResizeDetectorMaker({ + this._resizeDetector = elementResizeDetectorMaker({ strategy: "scroll", }); - this._erd.listenTo(this.elementRef.nativeElement, (element) => { + this._resizeDetector.listenTo(this.elementRef.nativeElement, (element) => { this.instance.refresh(); }); @@ -86,7 +86,7 @@ export class EditorComponent implements ControlValueAccessor, AfterViewInit, OnC public ngOnDestroy() { this._sub.unsubscribe(); - this._erd.uninstall(this.elementRef.nativeElement); + this._resizeDetector.uninstall(this.elementRef.nativeElement); } public codemirrorInit(config) { diff --git a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss index fb1550f5dd..98cef2b6d7 100644 --- a/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss +++ b/app/components/file/browse/file-explorer/file-explorer-tabs/file-explorer-tabs.scss @@ -5,8 +5,7 @@ bl-file-explorer-tabs { display: block; .tablist { height: $tab-height; - // border-bottom: 1px solid $border-color; - background: #e5e5e5; + background: $mercury-grey; display: flex; > .tab { diff --git a/app/components/file/browse/file-explorer/file-explorer-workspace.ts b/app/components/file/browse/file-explorer/file-explorer-workspace.ts index ecb1f1b80b..cadffef263 100644 --- a/app/components/file/browse/file-explorer/file-explorer-workspace.ts +++ b/app/components/file/browse/file-explorer/file-explorer-workspace.ts @@ -1,5 +1,5 @@ import { FileLoader, FileNavigator, FileTreeNode } from "app/services/file"; -import { log } from "app/utils"; +import { CloudPathUtils, log } from "app/utils"; import { BehaviorSubject, Observable } from "rxjs"; export interface FileSource { @@ -35,19 +35,16 @@ export interface CurrentNode { export class FileExplorerWorkspace { public sources: FileSource[]; public openedFiles: Observable; - // public openedFolder: Observable; public currentSource: Observable; public currentNode: Observable; private _currentSource = new BehaviorSubject(null); private _currentPath = new BehaviorSubject(""); private _openedFiles = new BehaviorSubject([]); - // private _openedFolder = new BehaviorSubject(null); constructor(data: FileNavigator | FileSource | FileSource[]) { this.sources = sourcesFrom(data); this.openedFiles = this._openedFiles.asObservable(); - // this.openedFolder = this._openedFolder.asObservable(); this.currentSource = this._currentSource.asObservable(); this._currentSource.next(this.sources.first()); @@ -87,7 +84,7 @@ export class FileExplorerWorkspace { public goBack() { const path = this._currentPath.value; if (path === "") { return; } - this.navigateTo(path.split("/").slice(0, -1).join("/"), this._currentSource.value); + this.navigateTo(CloudPathUtils.dirname(path), this._currentSource.value); } public isFileOpen(path: string, source?: FileSource): boolean { @@ -146,8 +143,9 @@ export class FileExplorerWorkspace { if (this.sources.length === 1) { return this.sources.first(); } else { - log.error("You must specify a source(FileNavigator) when using multi-source"); - throw Error("You must specify a source(FileNavigator) when using multi-source"); + const message = "You must specify a source(FileNavigator) when using multi-source"; + log.error(message); + throw Error(message); } } diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html index b849d1163e..79f04b2882 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.html @@ -15,9 +15,7 @@
-
- {{fileNavigator.error.message}} -
+
{{fileNavigator.error.message}}
.file-icon { > .fa-folder, > .fa-folder-open { - color: #f8d979; + color: $folder-color; } > .fa-file { @@ -114,6 +114,10 @@ bl-file-tree-view { } } } + + .tree-view-error { + white-space: pre; + } } .toggle-children-wrapper { diff --git a/app/components/file/details/file-details-view/file-details-view.html b/app/components/file/details/file-details-view/file-details-view.html index d0593ae0fa..71b7d31b1b 100644 --- a/app/components/file/details/file-details-view/file-details-view.html +++ b/app/components/file/details/file-details-view/file-details-view.html @@ -24,6 +24,6 @@
- File  {{filename}}  doesn't exists. + File  {{filename}}  doesn't exist.
diff --git a/app/components/task/details/output/task-outputs.component.ts b/app/components/task/details/output/task-outputs.component.ts index 1c4b816b63..edce26cbb5 100644 --- a/app/components/task/details/output/task-outputs.component.ts +++ b/app/components/task/details/output/task-outputs.component.ts @@ -95,7 +95,7 @@ export class TaskOutputsComponent implements OnChanges, OnDestroy { { name: "Node files", navigator: nodeNavigator, - openedFiles: ["stdout.txt", "stderr.txt", "wd/example-jobs/python/pi.py"], + openedFiles: ["stdout.txt", "stderr.txt"], }, { name: "Persisted output", navigator: taskOutputNavigator }, ]); diff --git a/app/styles/variables.scss b/app/styles/variables.scss index 5d7858a0c9..71258a5bc4 100644 --- a/app/styles/variables.scss +++ b/app/styles/variables.scss @@ -52,6 +52,8 @@ $link-color-hover : map-get($primary, 800); $validation-error-color : map-get($danger, 500); $label-text : $dusty-grey; +$folder-color: #f8d979; + $baseFontSize : 13px; $baseLineHeight : 1.4em; From 9726ee3bfd6d10373a36ace45b58d9167a0118fa Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 17:05:53 -0700 Subject: [PATCH 26/28] Show --- .../log-file-viewer/log-file-viewer.component.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/components/file/details/log-file-viewer/log-file-viewer.component.ts b/app/components/file/details/log-file-viewer/log-file-viewer.component.ts index e3779d7fd6..5a4434e242 100644 --- a/app/components/file/details/log-file-viewer/log-file-viewer.component.ts +++ b/app/components/file/details/log-file-viewer/log-file-viewer.component.ts @@ -34,6 +34,7 @@ export class LogFileViewerComponent implements OnChanges, OnDestroy, AfterViewIn public fileContentFailure = false; private _refreshInterval; + private _fileChangedSub: Subscription; constructor( private scrollableService: ScrollableService, @@ -56,6 +57,14 @@ export class LogFileViewerComponent implements OnChanges, OnDestroy, AfterViewIn this.currentSubscription.unsubscribe(); } + if (this._fileChangedSub) { + this._fileChangedSub.unsubscribe(); + } + if (changes.fileLoader) { + this._fileChangedSub = this.fileLoader.fileChanged.subscribe((file) => { + this._processProperties(file); + }); + } this.lines = []; this.loading = true; this.lastContentLength = 0; @@ -70,6 +79,7 @@ export class LogFileViewerComponent implements OnChanges, OnDestroy, AfterViewIn public ngOnDestroy() { // clear the refresh when the user navigates away clearInterval(this._refreshInterval); + this._fileChangedSub.unsubscribe(); } public toggleFollowLog() { From 47cfe9e8d38d30a685e2e9e22a5f88eb7a50342a Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 17:13:35 -0700 Subject: [PATCH 27/28] Preemted color --- app/components/pool/graphs/nodes-heatmap.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/components/pool/graphs/nodes-heatmap.component.ts b/app/components/pool/graphs/nodes-heatmap.component.ts index e6d3cab7b7..33a432a4b2 100644 --- a/app/components/pool/graphs/nodes-heatmap.component.ts +++ b/app/components/pool/graphs/nodes-heatmap.component.ts @@ -32,6 +32,7 @@ const stateTree: StateTree = [ { state: NodeState.running, color: runningColor }, { state: NodeState.waitingForStartTask, color: "#be93d9" }, { state: NodeState.offline, color: "#5b5b5b" }, + { state: NodeState.preempted, color: "#606060" }, { category: "transition", label: "Transition states", From 09564f6290d4de750849f0663e56ee3380d96a51 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 21 Sep 2017 18:15:19 -0700 Subject: [PATCH 28/28] Tree view expand current folder --- .../file-explorer/file-tree-view/file-tree-view.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts index 376b3b14ce..be89f53ea6 100644 --- a/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts +++ b/app/components/file/browse/file-explorer/file-tree-view/file-tree-view.component.ts @@ -48,6 +48,10 @@ export class FileTreeViewComponent implements OnChanges, OnDestroy { this._buildTreeRows(tree); })); } + + if (inputs.currentPath) { + this.expandPath(this.currentPath); + } } public ngOnDestroy() {