You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dlab.apache.org by dg...@apache.org on 2020/05/04 16:45:59 UTC

[incubator-dlab] branch DLAB-1775 created (now 3c76206)

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

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


      at 3c76206  [DLAB-1775]: Fixed bugs, changed file uploading

This branch includes the following new commits:

     new 3c76206  [DLAB-1775]: Fixed bugs, changed file uploading

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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


[incubator-dlab] 01/01: [DLAB-1775]: Fixed bugs, changed file uploading

Posted by dg...@apache.org.
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 <di...@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-unsubscribe@dlab.apache.org
For additional commands, e-mail: commits-help@dlab.apache.org