This is an automated email from the ASF dual-hosted git repository.

dgnatyshyn pushed a commit to branch DLAB-1775
in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git

commit 3c76206ec4ce05f8703d0e6322ffd608dfe51c41
Author: Dmytro Gnatyshyn <di1...@ukr.net>
AuthorDate: Mon May 4 19:45:22 2020 +0300

    [DLAB-1775]: Fixed bugs, changed file uploading
---
 .../resources/webapp/src/app/core/util/patterns.ts |   1 +
 .../bucket-browser/bucket-browser.component.html   | 138 +++++++++--------
 .../bucket-browser/bucket-browser.component.scss   | 170 ++++++++++++++++++++-
 .../bucket-browser/bucket-browser.component.ts     |  53 +++++--
 .../bucket-browser/bucket-data.service.ts          |  33 ++--
 .../folder-tree/folder-tree.component.html         |  25 ++-
 .../folder-tree/folder-tree.component.scss         |  64 +++++++-
 .../folder-tree/folder-tree.component.ts           |  48 +++++-
 .../detail-dialog/detail-dialog.component.html     |   8 +-
 .../detail-dialog/detail-dialog.component.scss     |  10 +-
 .../webapp/src/assets/styles/_dialogs.scss         |   7 +
 11 files changed, 453 insertions(+), 104 deletions(-)

diff --git 
a/services/self-service/src/main/resources/webapp/src/app/core/util/patterns.ts 
b/services/self-service/src/main/resources/webapp/src/app/core/util/patterns.ts
index ac9137f..cf8bfd3 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/core/util/patterns.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/core/util/patterns.ts
@@ -24,5 +24,6 @@ export const PATTERNS = {
   url: '[a-zA-Z0-9.://%#&\\.@:%-_\+~#=]*\.[^\s]*[a-zA-Z0-9]/+',
   nodeCountPattern: '^[1-9]\\d*$',
   integerRegex: '^[0-9]*$',
+  folderRegex: `[A-Za-z0-9]+$`,
   fullUrl: 
/^(http?|ftp|https):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+([.:])(\d{4}|com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*\/$/
 };
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
index 1de656e..1514359 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html
@@ -23,11 +23,12 @@
     <button type="button" class="close" 
(click)="dialogRef.close()">&times;</button>
   </header>
 
+<!--  <div class="dialog-content tabs">-->
   <div class="dialog-content tabs" [hidden]="!path" >
     <div class="submit m-bott-10 m-top-10">
       <span [matTooltip]="'You have not permission to upload data'" 
matTooltipPosition="above" matTooltipDisabled="{{this.bucketStatus.upload}}">
-        <button mat-raised-button type="button" class="butt action" 
[disabled]="!this.bucketStatus.upload">
-          <input [ngClass]="{'not-allowed': !this.bucketStatus.upload}" 
type="file" (change)="handleFileInput($event)">
+        <button mat-raised-button type="button" class="butt action" 
[disabled]="!this.bucketStatus.upload || allDisable">
+          <input [ngClass]="{'not-allowed': !this.bucketStatus.upload}" 
type="file" (change)="handleFileInput($event)" title="">
           Add file
         </button>
       </span>
@@ -36,96 +37,113 @@
           mat-raised-button
           type="button"
           class="butt action"
-          (click)="folderTreeComponent.addNewItem(selectedFolder, '', false)"
-          [disabled]="!this.bucketStatus.upload"
+          (click)="createFolder(selectedFolder)"
+          [disabled]="!this.bucketStatus.upload || allDisable"
         >
           Create folder
         </button>
       </span>
+      <span [matTooltip]="'You have not permission to delete data'" 
matTooltipPosition="above" matTooltipDisabled="{{this.bucketStatus.delete}}">
+        <button
+          type="button"
+          class="butt"
+          mat-raised-button
+          (click)="fileAction('delete')"
+          [disabled]="!selected?.length || !this.bucketStatus.delete || 
allDisable"
+
+        >
+        Delete
+      </button>
+      </span>
+
+      <span [matTooltip]="'You have not permission to download data'" 
matTooltipPosition="above" matTooltipDisabled="{{this.bucketStatus.download}}">
+       <button
+         type="button" class="butt"
+         mat-raised-button
+         (click)="fileAction('download')"
+         [disabled]="!selected?.length || !this.bucketStatus.download || 
allDisable"
+       >
+         Download
+       </button>
+      </span>
+
+
+<!--      <button type="button" class="butt" mat-raised-button 
[disabled]="this.addedFiles.length === 0" 
(click)="uploadNewFile()">Upload</button>-->
     </div>
-    <p class="path"><span>Bucket path:</span><span class="url"> 
{{path}}</span></p>
+    <p class="path"><span>Bucket path:</span><span class="url ellipsis"> 
{{path}}</span></p>
     <div class="backet-wrapper" id="scrolling">
       <div class="navigation">
-        <dlab-folder-tree (showFolderContent)=onFolderClick($event)> 
</dlab-folder-tree>
+        <dlab-folder-tree
+          (showFolderContent)=onFolderClick($event)
+          (disableAll) = dissableAll($event)
+          [folders] = folders
+        > </dlab-folder-tree>
       </div>
       <div class="directory">
-        <ul class="folder-tree" *ngIf="!addedFiles.length">
+        <div class="folder-item t_header"  *ngIf="folderItems.length && 
!addedFiles.length">
+          <div class="folder-item-wrapper header-wrapper folder-tree">
+            <div class="name"><span class="th_name">Name</span></div>
+            <div class="size"><span class="th_size">Size</span></div>
+            <div class="date"><span class="th_date">Last modified</span></div>
+          </div>
+        </div>
+<!--        <ul class="folder-tree" *ngIf="!addedFiles.length">-->
+        <ul class="folder-tree">
           <li *ngFor="let file of folderItems" class="folder-item" >
-
-            <div class="folder-item-wrapper" *ngIf="file.children" 
(click)="showItem(file)">
-              <div class="name name-folder"><i class="material-icons 
folder-icon" >folder</i><span>{{file.item}}</span></div>
-              <div class="size"></div>
-              <div class="progress-wrapper">
-                <div class="progres" *ngIf="file.isSelected"><div 
class="bar"></div></div>
-              </div>
+            <div class="folder-item-wrapper" *ngIf="file.children && 
file.item" (click)="showItem(file)">
+              <div class="name name-folder"><i class="material-icons 
folder-icon" >folder</i><span class="name-wrap">{{file.item}}</span></div>
+              <div class="size size-folder">-</div>
+              <div class="date" *ngIf="!file.isDownloading">-</div>
+<!--              <div class="progress-wrapper">-->
+<!--                <div class="progres" *ngIf="file.isSelected"><div 
class="bar"></div></div>-->
+<!--              </div>-->
             </div>
 
-            <div class="folder-item-wrapper"  
(click)="toggleSelectedFile(file)" *ngIf="!file.children">
+            <div class="folder-item-wrapper"  
(click)="toggleSelectedFile(file)" *ngIf="!file.children" 
[ngClass]="{'no-select': !this.bucketStatus.download && 
!this.bucketStatus.delete}">
               <div class="name name-file">
-                 <span *ngIf="this.bucketStatus.download || 
this.bucketStatus.download" class="empty-checkbox" [ngClass]="{'checked': 
file.isSelected}" (click)="toggleSelectedFile(file);$event.stopPropagation()" >
+                 <span *ngIf="this.bucketStatus.download || 
this.bucketStatus.delete" class="empty-checkbox" [ngClass]="{'checked': 
file.isSelected}" (click)="toggleSelectedFile(file);$event.stopPropagation()" >
                     <span class="checked-checkbox" 
*ngIf="file.isSelected"></span>
                   </span>
-                <span class="item-name" [ngClass]="{'removed-checkbox': 
!this.bucketStatus.download && 
!this.bucketStatus.download}">{{file.item}}</span>
+                <span class="item-name name-wrap" 
[ngClass]="{'removed-checkbox': !this.bucketStatus.download && 
!this.bucketStatus.delete}">{{file.item}}</span>
               </div>
-              <div class="size">{{file.size.size}}</div>
-              <div class="size" 
*ngIf="!file.isDownloading">{{file.size.lastModifiedDate }}</div>
+              <div class="size">{{file.size?.size}}</div>
+              <div class="date" 
*ngIf="!file.isDownloading">{{file.size?.lastModifiedDate }}</div>
               <div class="progress-wrapper" *ngIf="file.isDownloading">
                 <mat-progress-bar mode="indeterminate"></mat-progress-bar>
               </div>
             </div>
-
-          </li>
-        </ul>
-        <ul class="folder-tree">
-          <li *ngFor="let file of addedFiles" class="folder-item">
-            <div class="folder-item-wrapper">
-              <div class="name">{{file.item}}</div>
-              <div class="size">{{file.size}}MB</div>
-              <div class="progress-wrapper">
-                <mat-progress-bar mode="indeterminate" 
*ngIf="isUploading"></mat-progress-bar>
-              </div>
-              <div (click)="deleteAddedFile(file)"><i class="material-icons 
close">close</i></div>
-            </div>
           </li>
         </ul>
       </div>
     </div>
     <div class="text-center m-top-30 m-bott-30">
       <button type="button" class="butt" mat-raised-button 
(click)="this.dialogRef.close()">Close</button>
-      <span [matTooltip]="'You have not permission to download data'" 
matTooltipPosition="above" matTooltipDisabled="{{this.bucketStatus.download}}">
-       <button
-         *ngIf="!this.addedFiles.length"
-         type="button" class="butt butt-success"
-         mat-raised-button
-         (click)="fileAction('download')"
-         [disabled]="!selected?.length || !this.bucketStatus.download"
-       >
-         Download
-       </button>
-      </span>
-      <span [matTooltip]="'You have not permission to delete data'" 
matTooltipPosition="above" matTooltipDisabled="{{this.bucketStatus.delete}}">
-        <button
-          *ngIf="!this.addedFiles.length"
-          type="button"
-          class="butt butt-success"
-          mat-raised-button
-          (click)="fileAction('delete')"
-          [disabled]="!selected?.length || !this.bucketStatus.delete"
-
-        >
-        Delete
-      </button>
-      </span>
-
-      <button *ngIf="this.addedFiles.length !== 0" type="button" class="butt 
butt-success" mat-raised-button (click)="uploadNewFile()">Upload</button>
     </div>
   </div>
-
+  <div class="upload-window" *ngIf="isUploadWindowOpened">
+    <header class="upload-header">
+      <h4 class="modal-title">Upload files</h4>
+      <button type="button" class="close" 
(click)="closeUploadWindow()">&times;</button>
+    </header>
+    <ul class="upload-files">
+      <li *ngFor="let file of addedFiles" class="file">
+          <div class="name ellipsis" 
matTooltip="{{file.name}}">{{file.name}}</div>
+          <div class="upload-path ellipsis" 
matTooltip="{{file.path}}">{{file.path}}</div>
+          <div class="size">{{file.size}}MB</div>
+          <div class="state">
+            <button mat-raised-button (click)="uploadNewFile(file)" 
*ngIf="!file.isUploading && !file.doneUploading && 
!file.errorUploading">Upload</button>
+            <mat-progress-bar mode="indeterminate" 
*ngIf="file.isUploading"></mat-progress-bar>
+            <span *ngIf="file.uploaded">Uploaded</span>
+            <span *ngIf="file.errorUploading" class="error">Uploading 
error</span>
+          </div>
+          <div class="remove"><i (click)="deleteAddedFile(file)" 
class="material-icons close">close</i></div>
+      </li>
+    </ul>
+  </div>
   <div class="loading-block" *ngIf="!path">
     <div class="uploading">
       <p>Please wait until DLab loads bucket: <span 
class="strong">{{data.bucket}}</span>...</p>
       <mat-progress-bar mode="indeterminate"></mat-progress-bar>
     </div>
   </div>
-
 </div>
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
index eb0c55e..87a2461 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss
@@ -18,6 +18,8 @@
  */
 
 .bucket-browser {
+  font-size: 14px;
+  font-weight: 400;
   .loading-block{
     width: 80%;
     margin: 20% auto 0 auto;
@@ -35,11 +37,17 @@
   }
 
   .path{
-    margin: 0 4px 10px 4px;
+    margin: 0 4px 20px 4px;
     padding: 4px 4px 4px 20px;
     color: rgba(0,0,0,.87);
+    overflow: hidden;
+    white-space: nowrap;
+    display: flex;
     .url{
       font-weight: 600;
+      overflow: hidden;
+      display: block;
+      padding-left: 10px;
     }
   }
   bottom: 0;
@@ -89,24 +97,39 @@
     border-right: 2px solid rgba(0,0,0,.12);
     max-height: 500px;
     overflow: auto;
+    padding-top: 6px;
     .folder-tree{
 
       .folder{
         line-height: 30px;
       }
     }
+    .mat-tree-node{
+      min-height: auto;
+    }
 
   }
 
   .directory{
     max-height: 500px;
-    overflow: auto;
     flex: 2;
+    font-size: 14px;
+    font-weight: 400;
+    position: relative;
     .folder-tree{
+      overflow: auto;
+      max-height: 100%;
       .name{
         flex:2;
         display: flex;
         align-items: center;
+        overflow: hidden;
+        .name-wrap {
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          padding-right: 10px;
+        }
         &-folder{
           span{
             padding-left: 10px;
@@ -124,6 +147,15 @@
       }
       .size{
         flex:1;
+        &-folder{
+          padding-left: 34px;
+        }
+      }
+      .date{
+        flex:1;
+        .th_date{
+          font-size: 13px;
+        }
       }
       .progress-wrapper{
         flex:1;
@@ -175,11 +207,32 @@
 
   .folder-tree {
     padding: 20px 30px;
+    padding-top: 6px;
   }
 
   .folder-item{
      display: flex;
      align-items: center;
+    &.t_header{
+      top: -27px;
+      position: absolute;
+      left: 0;
+      right: 0;
+
+      .th_name{
+        padding-left: 35px;
+        font-size: 11px;
+      }
+
+      .th_size{
+        font-size: 11px;
+        padding-left: 3px;
+      }
+
+      .th_date{
+        font-size: 11px !important;
+      }
+    }
      .folder-item-wrapper{
        width: 100%;
        display: flex;
@@ -190,7 +243,7 @@
        i{
          color: rgb(232, 232, 232);
        }
-       &:hover{
+       &:hover:not(.header-wrapper){
          color: #00bcd4;
          transition: .3s ease-in-out;
          i{
@@ -206,11 +259,22 @@
            }
          }
        }
+       &.no-select{
+         pointer-events: none;
+         .name-wrap{
+           padding-left: 29px !important;
+         }
+       }
      }
    }
 }
 
+input[type='file'] {
+  opacity:0
+}
+
 .empty-checkbox {
+  min-width: 16px;
   width: 16px;
   height: 16px;
   border-radius: 2px;
@@ -241,9 +305,109 @@
   .check-box {
     background-color: red;
   }
+}
+
+.upload-window{
+  box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.42), 0 24px 20px 3px rgba(0, 0, 
0, 0.42), 0 9px 20px 8px rgba(0, 0, 0, 0.42);
+  position: fixed;
+  right: 0;
+  bottom: 0;
+  background-color: white;
+  width: 700px;
+  min-height: 100px;
+  color: black;
+  .upload-header{
+    padding-left: 30px;
+    background: #f6fafe;
+    height: 30px;
+    line-height: 30px;
+    position: relative;
+    .modal-title {
+      width: 90%;
+      margin: 0;
+      overflow: hidden;
+      white-space: nowrap;
+      text-overflow: ellipsis;
+      font-weight: 600;
+      color: #455c74;
+      font-size: 14px;
+      background: #f6fafe;
+  }
+    .close{
+      position: absolute;
+      top: 0;
+      right: 0;
+      height: 30px;
+      width: 30px;
+      font-size: 14px;
+      font-weight: 300;
+      border: 0;
+      background: none;
+      color: #577289;
+      outline: none;
+      cursor: pointer;
+      transition: all 0.45s ease-in-out;
+      &:hover{
+        color: #36afd5;
+      }
+    }
 
+  }
+  .upload-files{
+    padding: 5px;
+    .file{
+      padding: 2px;
+      display: flex;
+      font-size: 12px;
+      .name{
+        flex:12;
+        padding-right: 5px;
+      }
+
+      .upload-path{
+        flex:12;
+        padding-right: 5px;
+      }
+
+      .size{
+        flex:4;
+      }
+      .state{
+        flex:6;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        .mat-raised-button{
+          width: 60px;
+          padding: 5px;
+          border-radius: 0;
+          font-style: normal;
+          font-weight: 600;
+          font-size: 11px;
+          font-family: "Open Sans", sans-serif;
+          color: #577289;
+          line-height: 8px;
+        }
+      }
+      .remove{
+        flex:1;
+        display: flex;
+        align-items: center;
+        .close{
+          color: #577289;
+          font-size: 14px;
+          cursor: pointer;
+          &:hover{
+            color: #f1696e;
+          }
+        }
+      }
+    }
+  }
 }
 
+
+
 @media only screen and (max-height: 840px) {
   .backet-wrapper {
     height: 45vh;
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
index 59e447f..1698a33 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts
@@ -40,13 +40,16 @@ export class BucketBrowserComponent implements OnInit {
   public pathInsideBucket = '';
   public bucketName = '';
   public endpoint = '';
+  public isUploadWindowOpened = false;
 
   @ViewChild(FolderTreeComponent, {static: true}) folderTreeComponent;
   public selectedFolder: any;
   private isUploading: boolean;
-  private selected: any[];
+  public selected: any[];
   private uploadForm: FormGroup;
-  private bucketStatus: object;
+  public bucketStatus;
+  public allDisable: boolean;
+  public folders: any[];
 
   constructor(
     @Inject(MAT_DIALOG_DATA) public data: any,
@@ -81,9 +84,15 @@ export class BucketBrowserComponent implements OnInit {
       const file = event.target.files[0];
       this.uploadForm.get('file').setValue(file);
       const newAddedFiles = Object['values'](event.target.files).map(v => (
-        {item: v['name'], 'size': (v['size'] / 1048576).toFixed(2)} as unknown 
as TodoItemNode  ));
-      this.addedFiles = [...newAddedFiles];
+        {name: v['name'], file: file, 'size': (v['size'] / 
1048576).toFixed(2), path: this.path, isUploading: false, uploaded: false, 
errorUploading: false}));
+      this.addedFiles = [...this.addedFiles, ...newAddedFiles];
+      this.isUploadWindowOpened = !!this.addedFiles.length;
     }
+    event.target.files = [];
+  }
+
+  public closeUploadWindow() {
+    this.isUploadWindowOpened = false;
   }
 
 
@@ -98,19 +107,24 @@ export class BucketBrowserComponent implements OnInit {
   }
 
   filesPicked(files) {
-
     Array.prototype.forEach.call(files, file => {
       this.addedFiles.push(file.webkitRelativePath);
     });
   }
 
+  public dissableAll(event) {
+    this.allDisable = event;
+  }
+
   public onFolderClick(event) {
+    this.folderItems.forEach(item => item.isSelected = false);
+    this.selected = this.folderItems.filter(item => item.isSelected);
     this.selectedFolder = event.flatNode;
     this.folderItems = event.element ? event.element.children : event.children;
     if (this.folderItems) {
-      const folders = this.folderItems.filter(v => v.children).sort((a, b) => 
a.item > b.item ? 1 : -1);
+      this.folders = this.folderItems.filter(v => v.children).sort((a, b) => 
a.item > b.item ? 1 : -1);
       const files = this.folderItems.filter(v => !v.children).sort((a, b) => 
a.item > b.item ? 1 : -1);
-      this.folderItems = [...folders, ...files];
+      this.folderItems = [...this.folders, ...files];
       this.path = event.path;
       this.pathInsideBucket = this.path.indexOf('/') !== -1 ?  
this.path.slice(this.path.indexOf('/') + 1) + '/' : '';
       this.bucketName = this.path.substring(0, this.path.indexOf('/')) || 
this.path;
@@ -123,25 +137,34 @@ export class BucketBrowserComponent implements OnInit {
     this.addedFiles.splice(this.addedFiles.indexOf(file), 1);
   }
 
-  private uploadNewFile() {
-    const path = 
`${this.pathInsideBucket}${this.uploadForm.get('file').value.name}`;
-
+  private uploadNewFile(file) {
+    const path = `${file.path}/${file.name}`;
     const formData = new FormData();
-    formData.append('file', this.uploadForm.get('file').value);
+    formData.append('file', file.file);
     formData.append('object', path);
     formData.append('bucket', this.bucketName);
     formData.append('endpoint', this.endpoint);
-    this.isUploading = true;
+    file.isUploading = true;
+    console.log(formData);
     this.bucketBrowserService.uploadFile(formData)
       .subscribe(() => {
         this.bucketDataService.refreshBucketdata(this.data.bucket, 
this.data.endpoint);
-        this.addedFiles = [];
-        this.isUploading = false;
+        file.isUploading = false;
+        file.uploaded = true;
         this.toastr.success('File successfully uploaded!', 'Success!');
-      }, error => this.toastr.error(error.message || 'File uploading error!', 
'Oops!')
+      }, error => {
+        this.toastr.error(error.message || 'File uploading error!', 'Oops!');
+        file.errorUploading = true;
+        file.isUploading = false;
+      }
     );
   }
 
+  public createFolder(folder) {
+    this.allDisable = true;
+    this.folderTreeComponent.addNewItem(folder, '', false);
+  }
+
   public fileAction(action) {
     this.selected = this.folderItems.filter(item => item.isSelected);
     const selected = this.folderItems.filter(item => item.isSelected)[0];
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts
index f87c575..7c92a8a 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts
@@ -21,8 +21,8 @@ import { Injectable } from '@angular/core';
 import { BehaviorSubject} from 'rxjs';
 import {BucketBrowserService, TodoItemNode} from 
'../../core/services/bucket-browser.service';
 
-const array = [{'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '4.txt', 
'size': '18 bytes', 'creationDate': '21-4-2020 11:36:36'}, {'bucket': 
'ofuks-1304-prj1-local-bucket', 'object': '5.txt', 'size': '18 bytes', 
'creationDate': '21-4-2020 11:56:46'}, {'bucket': 
'ofuks-1304-prj1-local-bucket', 'object': 'Untitled', 'size': '5 bytes', 
'creationDate': '13-4-2020 03:39:11'}, {'bucket': 
'ofuks-1304-prj1-local-bucket', 'object': 'adasdas', 'size': '1 KB', 
'creationDate': '15-4-2020 02:17 [...]
-
+const array = [{'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 
'4.txt/dsafaraorueajkegrgavhsfnvgahsfgsdjfhagsdjfg497frgfhsdajfsgdafjs', 
'size': '18 bytes', 'lastModifiedDate': '21-4-2020 11:36:36'}, {'bucket': 
'ofuks-1304-prj1-local-bucket', 'object': '51.txt', 'size': '18 bytes', 
'lastModifiedDate': '21-4-2020 11:56:46'}, {'bucket': 
'ofuks-1304-prj1-local-bucket', 'object': 'Untitlsed', 'size': '5 bytes', 
'lastModifiedDate': '13-4-2020 03:39:11'}, {'bucket': 'ofuks-1304-prj1-local- 
[...]
+// const array = [];
 
 @Injectable()
 export class BucketDataService {
@@ -51,9 +51,16 @@ export class BucketDataService {
   }
   public buildFileTree(obj: {[key: string]: any}, level: number): 
TodoItemNode[] {
       return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => {
+        if (key === '') {
+          return accumulator;
+        }
         const value = obj[key];
         const node = new TodoItemNode();
         node.item = key;
+        if (value === '') {
+          node.children = this.buildFileTree({}, level + 1);
+          return accumulator.concat(node);
+        }
         if (Object.keys(value).length) {
           if (typeof value === 'object') {
             node.children = this.buildFileTree(value, level + 1);
@@ -72,7 +79,7 @@ export class BucketDataService {
         if (isFile) {
           parent.children.push(name as TodoItemNode);
         } else {
-          parent.children.unshift({item: name, children: []} as TodoItemNode);
+          parent.children.unshift({item: name ? name + '/' : name, children: 
[]} as TodoItemNode);
           this._bucketData.next(this.data);
         }
       }
@@ -83,6 +90,11 @@ export class BucketDataService {
       this._bucketData.next(this.data);
     }
 
+  public removeItem(parent) {
+     parent.children.shift();
+     this._bucketData.next(this.data);
+    }
+
     public processFiles = (files, target) => {
       let pointer = target;
       files.forEach((file) => {
@@ -94,15 +106,18 @@ export class BucketDataService {
     }
 
     public processFolderArray = (acc, curr) => {
-      const files = curr.object.split('/').filter(x => x.length > 0);
+      // const files = curr.object.split('/').filter(x => x.length > 0);
+      const files = curr.object.split('/');
       this.processFiles(files, acc);
       return acc;
     }
 
-    public convertToFolderTree = (data) => data
-      .reduce(
-        this.processFolderArray,
-        {}
-      )
+    public convertToFolderTree = (data) => {
+      const finalData = data.reduce(this.processFolderArray, {});
+      if (Object.keys(finalData).length === 0) {
+        return '';
+      }
+      return finalData;
+    }
 
 }
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
index b663e78..4e29cc8 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.html
@@ -7,13 +7,24 @@
   {{node.item}}
 </mat-tree-node>
 
-  <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" 
matTreeNodePadding>
-    <button mat-icon-button disabled></button>
-    <mat-form-field>
-      <mat-label>New folder</mat-label>
-      <input matInput #itemValue>
-    </mat-form-field>
-    <button mat-button (click)="saveNode(node, itemValue.value)">Save</button>
+  <mat-tree-node *matTreeNodeDef="let node; when: hasNoContent" 
matTreeNodePadding class="input-node">
+    <form class="add-folder-form">
+      <mat-form-field>
+        <mat-label>New folder</mat-label>
+        <input matInput #itemValue [formControl]="folderFormControl" 
[errorStateMatcher]="matcher">
+        <mat-error *ngIf="!folderFormControl.hasError('required') && 
!folderFormControl.hasError('isDuplicate')">
+         Folder name can only contain latin letters, numbers end special 
characters exepting  #, ?, /, \, "
+        </mat-error>
+        <mat-error *ngIf="folderFormControl.hasError('required')">isDuplicate
+          Folder name is <strong>required</strong>
+        </mat-error>
+        <mat-error *ngIf="folderFormControl.hasError('isDuplicate')">
+          Folder with this name already exist
+        </mat-error>
+      </mat-form-field>
+      <button (click)="saveNode(node, itemValue.value)" [ngClass]="{'check': 
folderFormControl.valid && folderFormControl.dirty}" mat-icon-button class="btn 
action-btn" [disabled]="!folderFormControl.valid || 
!folderFormControl.dirty"><span><i class="material-icons 
">check</i></span></button>
+      <button (click)="removeItem(node)" mat-icon-button class="btn close 
action-btn"><span ><i class="material-icons ">close</i></span></button>
+    </form>
   </mat-tree-node>
 
   <mat-tree-node *matTreeNodeDef="let node; when: hasChild" matTreeNodePadding>
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
index e9902af..80d51f9 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.scss
@@ -1,6 +1,14 @@
 
 .folder{
   padding-left: 5px;
+  max-width: 150px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.mat-tree{
+  font-family: 'Open Sans', sans-serif;
 }
 
 .folder-icon{
@@ -24,10 +32,23 @@
 
 }
 
-.mat-tree-node{
+.add-folder-form{
+  display: flex;
+  align-items: center;
+}
+
+.mat-tree-node:not(.input-node){
   cursor: pointer;
   transition: .3s;
   overflow: unset;
+  min-height: auto;
+  button.mat-icon-button{
+    width: 25px;
+    height: 25px;
+    line-height: 28px;
+    //display: flex;
+    //align-items: center;
+  }
   &:hover{
     color: #00bcd4;
     i{
@@ -36,3 +57,44 @@
   }
 }
 
+.input-node {
+  overflow: unset;
+
+  button.mat-icon-button {
+    &.action-btn {
+      color: lightgrey;
+      font-size: 20px;
+      cursor: not-allowed;
+    }
+
+    &.check {
+      color: #49af38;
+      cursor: pointer;
+
+      &:hover {
+        border-color: #49af38;
+        background: #f9fafb;
+      }
+    }
+
+    &.close {
+      color: #f1696e;
+      cursor: pointer;
+
+      &:hover {
+        border-color: #f1696e;
+        background: #f9fafb;
+      }
+    }
+  }
+
+  .mat-error {
+    background-color: #ffffff;
+  }
+}
+
+
+
+
+
+
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
index 179a4c1..5fdfda9 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts
@@ -1,12 +1,20 @@
-import {Component, OnInit, AfterViewInit, Output, EventEmitter, OnDestroy} 
from '@angular/core';
+import {Component, OnInit, AfterViewInit, Output, EventEmitter, OnDestroy, 
Input} from '@angular/core';
 import {SelectionModel} from '@angular/cdk/collections';
 import {FlatTreeControl} from '@angular/cdk/tree';
 import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
 import {BucketBrowserService, TodoItemFlatNode, TodoItemNode} from 
'../../../core/services/bucket-browser.service';
 import {BucketDataService} from '../bucket-data.service';
 import {Subscription} from 'rxjs';
-
-
+import {FormControl, FormGroupDirective, NgForm, Validators} from 
'@angular/forms';
+import {ErrorStateMatcher} from '@angular/material/core';
+import {PATTERNS} from '../../../core/util';
+
+export class MyErrorStateMatcher implements ErrorStateMatcher {
+  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm 
| null): boolean {
+    const isSubmitted = form && form.submitted;
+    return !!(control && control.invalid && (control.dirty));
+  }
+}
 
 @Component({
   selector: 'dlab-folder-tree',
@@ -17,6 +25,9 @@ import {Subscription} from 'rxjs';
 export class FolderTreeComponent implements OnInit, OnDestroy {
 
   @Output() showFolderContent: EventEmitter<any> = new EventEmitter();
+  @Output() disableAll: EventEmitter<any> = new EventEmitter();
+  @Input() folders;
+
   private folderTreeSubs;
   private path = [];
   private selectedFolder: TodoItemFlatNode;
@@ -29,6 +40,7 @@ export class FolderTreeComponent implements OnInit, OnDestroy 
{
   private treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>;
   public dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>;
 
+
   private checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* 
multiple */);
 
   constructor(
@@ -85,6 +97,21 @@ export class FolderTreeComponent implements OnInit, 
OnDestroy {
     this.bucketDataService._bucketData.next([]);
   }
 
+  folderFormControl = new FormControl('', [
+    Validators.required,
+    Validators.pattern(PATTERNS.folderRegex),
+    this.duplicate.bind(this)
+  ]);
+
+  matcher = new MyErrorStateMatcher();
+
+  private duplicate(control) {
+    if (control && control.value) {
+      const isDublicat = this.folders.slice(1).some(folder => 
folder.item.toLocaleLowerCase() === control.value.toLowerCase());
+      return isDublicat ? { isDuplicate: true } : null;
+    }
+  }
+
   private showItem(el) {
     if (el) {
       this.treeControl.expand(el);
@@ -203,9 +230,24 @@ export class FolderTreeComponent implements OnInit, 
OnDestroy {
     this.treeControl.expand(node);
   }
 
+  private removeItem(node: TodoItemFlatNode) {
+    const parentNode = this.flatNodeMap.get(this.getParentNode(node));
+    this.bucketDataService.removeItem(parentNode!);
+    this.resetForm();
+  }
+
   private saveNode(node: TodoItemFlatNode, itemValue: string) {
     const nestedNode = this.flatNodeMap.get(node);
     this.bucketDataService.updateItem(nestedNode!, itemValue);
+    this.resetForm();
+    this.folderFormControl.updateValueAndValidity();
+    this.folderFormControl.markAsPristine();
+  }
 
+  private resetForm(){
+    this.folderFormControl.setValue('');
+    this.folderFormControl.updateValueAndValidity();
+    this.folderFormControl.markAsPristine();
+    this.disableAll.emit(false);
   }
 }
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
index cd39c46..09351f0 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html
@@ -68,9 +68,9 @@
                  [ngClass]="{'not-allow': !this.bucketStatus.view}"
             >
               <p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && 
notebook.account_name">{{ DICTIONARY[PROVIDER].account }}
-                <span class="bucket-info" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.account_name}}</span></p>
+                <span class="bucket-info bucket-link" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.account_name}}</span></p>
               <p *ngIf="notebook.bucket_name">{{ 
DICTIONARY[PROVIDER].container }} <span
-                  class="bucket-info" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.bucket_name }}</span></p>
+                  class="bucket-info bucket-link" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.bucket_name }}</span></p>
             </div>
             <p>Shared endpoint bucket: &nbsp;</p>
             <div class="links_block" 
(click)="bucketBrowser(notebook.shared_bucket_name, notebook.endpoint, 
this.bucketStatus.view)"
@@ -80,9 +80,9 @@
                  [ngClass]="{'not-allow': !this.bucketStatus.view}"
             >
               <p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && 
notebook.shared_account_name">{{ DICTIONARY[PROVIDER].account }}
-                <span class="bucket-info" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.shared_account_name}}</span></p>
+                <span class="bucket-info bucket-link" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.shared_account_name}}</span></p>
               <p *ngIf="notebook.shared_bucket_name">{{ 
DICTIONARY[PROVIDER].container }}
-                <span class="bucket-info" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.shared_bucket_name }}</span></p>
+                <span class="bucket-info bucket-link" [ngClass]="{'not-allow': 
!this.bucketStatus.view}">{{ notebook.shared_bucket_name }}</span></p>
             </div>
             <br />
 
diff --git 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
index 24d273f..2f36e20 100644
--- 
a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
+++ 
b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.scss
@@ -89,6 +89,12 @@
   cursor: pointer;
 }
 
-.not-allow{
-  cursor: not-allowed;
+.bucket-link{
+  color: #35afd5;
+  &.not-allow{
+    cursor: not-allowed;
+    color: $blue-grey-color;
+  }
 }
+
+
diff --git 
a/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
 
b/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
index c5ac335..4fa2ae0 100644
--- 
a/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
+++ 
b/services/self-service/src/main/resources/webapp/src/assets/styles/_dialogs.scss
@@ -343,6 +343,13 @@ mat-dialog-container {
   overflow: auto;
 }
 
+.bucket-browser{
+  .mat-form-field-appearance-legacy .mat-form-field-subscript-wrapper {
+    width: calc(100% + 77px);
+  }
+}
+
+
 @media screen and (max-width: 1280px) {
   .modal-fullscreen {
     max-width: 100vw !important;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@dlab.apache.org
For additional commands, e-mail: commits-h...@dlab.apache.org

Reply via email to