zeppelin-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zjf...@apache.org
Subject [zeppelin] branch web_vue updated: ZEPPELIN-4376, ZEPPELIN-4379 Zeppelin Web Vue - Design as per Mocks and Recycle Bin
Date Mon, 21 Oct 2019 05:02:16 GMT
This is an automated email from the ASF dual-hosted git repository.

zjffdu pushed a commit to branch web_vue
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/web_vue by this push:
     new 76b4564  ZEPPELIN-4376, ZEPPELIN-4379 Zeppelin Web Vue - Design as per Mocks and
Recycle Bin
76b4564 is described below

commit 76b456450792221d5dd9809a36a1434c4955709d
Author: Malay Majithia <malay.majithia@gmail.com>
AuthorDate: Thu Oct 17 02:56:05 2019 +0530

    ZEPPELIN-4376, ZEPPELIN-4379 Zeppelin Web Vue - Design as per Mocks and Recycle Bin
    
    ### What is this PR for?
    - ZEPPELIN-4376 Design change as per new mockups (except Notebook view)
    - Added Search button placeholder (actual functionality will be implemented with Foldering)
    - ZEPPELIN-4379 Recycle Bin: Restore Note, Delete Permanently
    - A different view for Deleted Note
    - Fixed the bug which used to move the open note to the error state post note operations
    
    Thanks, kmaynk for the mockups.
    Mockups: https://www.figma.com/file/F0m6EJLjoVmxFuYfM2FTUe/Mocks-V1
    
    ### What type of PR is it?
    Feature
    
    ### What is the Jira issue?
    * https://issues.apache.org/jira/browse/ZEPPELIN-4376
    * https://issues.apache.org/jira/browse/ZEPPELIN-4379
    
    ### How should this be tested?
    * Start the zeppelin server
    * Go to the zeppelin-web-vue folder
    * Run npm install (first time)
    * Run npm run serve to run Zeppelin web using vue.js on localhost:8081 ( can be changed
through .env file)
    
    ### Screenshots (if appropriate)
    <img width="1440" alt="Screen Shot 2019-10-15 at 3 00 57 AM" src="https://user-images.githubusercontent.com/1881135/66784862-40075300-eef9-11e9-850b-aa96a61e8c82.png">
    <img width="633" alt="Screen Shot 2019-10-15 at 3 01 26 AM" src="https://user-images.githubusercontent.com/1881135/66784863-40075300-eef9-11e9-9d30-c832c76a6748.png">
    <img width="1440" alt="Screen Shot 2019-10-15 at 3 01 48 AM" src="https://user-images.githubusercontent.com/1881135/66784864-40075300-eef9-11e9-8540-4eaa8b7ec4ae.png">
    <img width="1440" alt="Screen Shot 2019-10-15 at 3 08 40 AM" src="https://user-images.githubusercontent.com/1881135/66784865-409fe980-eef9-11e9-95bf-1b8b1d23a90b.png">
    
    ### Questions:
    * Does the licenses files need update? - No
    * Is there breaking changes for older versions? - No
    * Does this needs documentation? - No
    
    Author: Malay Majithia <malay.majithia@gmail.com>
    
    Closes #3487 from malayhm/ZEPPELIN-4138-1 and squashes the following commits:
    
    7107c5d06 [Malay Majithia] TopMenu: Restore and Delete Permanently Renamed Delete Temporary
to Move to Trash in the labels and in the codebase
    ed7574c9c [Malay Majithia] Rename RecycleBin component to Trash
    f25bb4778 [Malay Majithia] Rename: Recycle Bin -> Trash
    76bb71fc0 [Malay Majithia] Don't close the current tab if it's deleted Removed unnecessary
reload list
    c1836116c [Malay Majithia] Don't delete the open notebook rather use the param note Id
    9baf1dae2 [Malay Majithia] Restore Notebook, Delete Permanently, Added Item Context menu
options Moved Reload to the Top Menu
    9c08a9915 [Malay Majithia] Array merge on note list reload to preserve the paragraph data
    1de9593cb [Malay Majithia] Rename Notebook - name / path
    30a19d4d2 [Malay Majithia] Removed commented jquery.menu.scss code
    8f97e1fe2 [Malay Majithia] ZEPPELIN-4376 Design change as per new mockups - Recycle bin
fixes - Delete Notebook permanently - WIP - Search Notebook (sidebar) - WIP
---
 zeppelin-web-vue/src/App.vue                       |  21 ++-
 zeppelin-web-vue/src/assets/jquery.menu.scss       |  29 ++--
 zeppelin-web-vue/src/classes/web-socket.js         |   2 +-
 .../src/components/Layout/Connectivity.vue         |  40 +++++
 zeppelin-web-vue/src/components/Layout/Header.vue  |  48 ++++--
 .../src/components/Layout/LeftNavBar.vue           |   2 +-
 .../src/components/Layout/LeftSideBar.vue          |   6 +-
 zeppelin-web-vue/src/components/Layout/TopMenu.vue |  88 ++++++++--
 .../src/components/Notebook/Controls.vue           |  90 +++++++---
 .../src/components/Notebook/NoteTree.vue           | 107 +++++++++++-
 .../src/components/Notebook/RecycleBin.vue         | 110 ------------
 .../src/components/Notebook/Rename.vue             |  91 ++++++++++
 zeppelin-web-vue/src/components/Notebook/Trash.vue | 190 +++++++++++++++++++++
 zeppelin-web-vue/src/i18n.js                       |  11 +-
 zeppelin-web-vue/src/mixins/array_utils.js         |   8 +
 zeppelin-web-vue/src/services/command-manager.js   |  15 +-
 zeppelin-web-vue/src/services/notebook-utils.js    |  29 ++--
 zeppelin-web-vue/src/stores/notebook_store.js      |   5 +-
 zeppelin-web-vue/src/stores/tab_manager_store.js   |   6 +-
 19 files changed, 681 insertions(+), 217 deletions(-)

diff --git a/zeppelin-web-vue/src/App.vue b/zeppelin-web-vue/src/App.vue
index 31f5eb9..5069eba 100644
--- a/zeppelin-web-vue/src/App.vue
+++ b/zeppelin-web-vue/src/App.vue
@@ -13,7 +13,7 @@
       </SplitArea>
     </Split>
 
-    <StatusBar />
+    <!-- <StatusBar /> -->
 
     <GlobalEvents
       @keyup.ctrl.n="executeCommand('note', 'show-create')"
@@ -21,8 +21,9 @@
       @keyup.ctrl.r="executeCommand('note', 'run-all')"
     />
 
-    <Create />
-    <Import />
+    <CreateNote />
+    <ImportNote />
+    <RenameNote />
   </div>
 </template>
 
@@ -34,14 +35,15 @@ import ws from '@/services/ws-helper'
 
 import Header from '@/components/Layout/Header.vue'
 import LeftSidebar from '@/components/Layout/LeftSideBar.vue'
-import StatusBar from '@/components/Layout/StatusBar.vue'
+// import StatusBar from '@/components/Layout/StatusBar.vue'
 
-import Create from '@/components/Notebook/Create.vue'
-import Import from '@/components/Notebook/Import.vue'
+import CreateNote from '@/components/Notebook/Create.vue'
+import ImportNote from '@/components/Notebook/Import.vue'
+import RenameNote from '@/components/Notebook/Rename.vue'
 
 export default {
   name: 'App',
-  components: { GlobalEvents, Header, LeftSidebar, StatusBar, Create, Import },
+  components: { GlobalEvents, Header, LeftSidebar, CreateNote, ImportNote, RenameNote },
   created () {
     document.title = 'Zeppelin Notebook'
   },
@@ -80,6 +82,9 @@ export default {
 body {
   color: #2c3e50;
 
+  font-size: 14px;
+  line-height: 1.5;
+
   a {
     text-decoration: none;
     color: #333;
@@ -99,7 +104,7 @@ body {
   width: 100%;
 
   > .split {
-    height: calc(100% - 67px - 24px);
+    height: calc(100% - 40px);
     border-top: 1px solid #F1F1F1;
   }
 
diff --git a/zeppelin-web-vue/src/assets/jquery.menu.scss b/zeppelin-web-vue/src/assets/jquery.menu.scss
index 5d24d04..8e181d5 100644
--- a/zeppelin-web-vue/src/assets/jquery.menu.scss
+++ b/zeppelin-web-vue/src/assets/jquery.menu.scss
@@ -1,18 +1,18 @@
 #menu-bar {
   * {
-    box-sizing: content-box;
+    box-sizing: border-box;
   }
 
   .menu-top-mask {
     height: 2px;
-    background-color: #fff;
     z-index:1001;
   }
 
   ul.main-menu {
     list-style-type: none;
-    margin: 0 0 0 3px;
     padding: 0;
+    margin: 0;
+    padding-top: 6px;
     font-size: 14px;
     font-weight: 400;
 
@@ -20,21 +20,14 @@
       margin: 0;
       display: inline-block;
       list-style-type: none;
-      padding: 0 8px;
-      line-height: 28px;
-      vertical-align: middle;
+      padding: 4px 8px;
+      height: 34px;
       cursor: pointer;
       border: 1px solid transparent;
 
       &.active-menu {
-        background-color: #fff;
-        border-color: #ccc;
-        border: 1px solid #BDBDBD;
-        border-bottom-color: transparent;
-
-        &:hover{
-          background-color: #fff;
-        }
+        background-color: rgba(211, 211, 211, 0.2);
+        border-radius: 2px 2px 0px 0px;
       }
 
       .separator {
@@ -48,9 +41,6 @@
         padding: 0;
         margin: 0;
         display: none;
-        border-width:1px;
-        border-style: solid;
-        border-color: #ccc;
         background-color: #fff;
         -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
         -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
@@ -60,7 +50,8 @@
 
     li {
       &:hover{
-        background-color: whiteSmoke; /*#fef7cb;*/
+        background-color: rgba(211, 211, 211, 0.2);
+        border-radius: 2px 2px 0px 0px;
       }
 
       a {
@@ -95,7 +86,7 @@
               cursor:default;
               background-color: #fff;
             }
-            padding-right: 40px;
+            padding: 4px 40px 4px 0;
 
             span {
               font-size: 11px;
diff --git a/zeppelin-web-vue/src/classes/web-socket.js b/zeppelin-web-vue/src/classes/web-socket.js
index 32d26eb..b474950 100644
--- a/zeppelin-web-vue/src/classes/web-socket.js
+++ b/zeppelin-web-vue/src/classes/web-socket.js
@@ -96,7 +96,7 @@ export default class WsConnection {
           // Pending - open the note tab data.note
           break
         case 'NOTES_INFO':
-          this.store.dispatch('setNoteMenu', data)
+          this.store.dispatch('setNoteList', data)
           break
         case 'NOTE':
           this.store.dispatch('setNoteContent', data)
diff --git a/zeppelin-web-vue/src/components/Layout/Connectivity.vue b/zeppelin-web-vue/src/components/Layout/Connectivity.vue
new file mode 100644
index 0000000..bf95737
--- /dev/null
+++ b/zeppelin-web-vue/src/components/Layout/Connectivity.vue
@@ -0,0 +1,40 @@
+<template>
+  <div
+    id="connection-status"
+    class="status-bar-widget"
+  >
+    <a-tooltip placement="bottom">
+      <template slot="title">
+        <span>Server Connectivity</span>
+      </template>
+      <div
+        class="ConnectionIndicator"
+        :class="'ConnectionIndicator--' + connectivityStatus"
+      >
+        <div class="Status">
+          <div class="Status__circle Status__circle--static"></div>
+          <div class="Status__circle Status__circle--animated Status__circle--pulse"></div>
+        </div>
+        <div
+          class="status-label"
+        >
+          {{ connectivityStatus.replace('trying', 'trying to connect') }}
+        </div>
+      </div>
+    </a-tooltip>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Connectivity',
+  computed: {
+    connectivityStatus () {
+      return this.$store.state.webSocketStatus
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>
diff --git a/zeppelin-web-vue/src/components/Layout/Header.vue b/zeppelin-web-vue/src/components/Layout/Header.vue
index a5d3fa4..45cdffd 100644
--- a/zeppelin-web-vue/src/components/Layout/Header.vue
+++ b/zeppelin-web-vue/src/components/Layout/Header.vue
@@ -3,47 +3,67 @@
     <div style="display: flex">
       <router-link
         to="/"
-        class="logo pt-3 pl-2"
+        class="logo pt-2 pl-2"
       >
-        <img alt="Zeppelin logo" src="@/assets/zepLogo.png">
+        <img alt="Zeppelin logo" src="@/assets/zepLogoW.png">
       </router-link>
 
-      <div>
-        <h5 class="pt-2 mb-1">
-          Zeppelin Notebook
-        </h5>
-        <TopMenu />
-      </div>
+      <h5>
+        Zeppelin Notebook
+      </h5>
+
+      <TopMenu />
+
+      <Connectivity class="connectivity"/>
     </div>
   </div>
 </template>
 
 <script>
 import TopMenu from './TopMenu.vue'
+import Connectivity from './Connectivity.vue'
 
 export default {
   name: 'Header',
-  components: { TopMenu }
+  components: { TopMenu, Connectivity }
 }
 </script>
 
 <style lang="scss" scoped>
 #header {
-  height: 67px;
+  height: 40px;
   margin: 0;
   padding: 0;
+  background: #2C2C2C;
+  color: #FFFFFF;
+
+  h5 {
+    color: #FFFFFF;
+    font-style: normal;
+    font-weight: 600;
+    font-size: 16px;
+    border-right: 0.5px solid rgba(211, 211, 211, 0.2);
+    vertical-align: middle;
+    line-height: 20px;
+    padding-left: 12px;
+    padding-right: 20px;
+    margin-right: 20px;
+    margin-top: 10px;
+  }
 
   .logo {
     display: block;
-    width: 60px;
+    width: 50px;
 
     img {
-      width: 40px;
+      height: 20px;
     }
   }
 
-  h5 {
-    padding-left: 12px;
+  .connectivity {
+    position: absolute;
+    right: 0;
+    top: 7px;
   }
 }
 </style>
diff --git a/zeppelin-web-vue/src/components/Layout/LeftNavBar.vue b/zeppelin-web-vue/src/components/Layout/LeftNavBar.vue
index b536419..4ce2bb2 100644
--- a/zeppelin-web-vue/src/components/Layout/LeftNavBar.vue
+++ b/zeppelin-web-vue/src/components/Layout/LeftNavBar.vue
@@ -59,7 +59,7 @@
           >
             <a-tooltip placement="right">
               <template slot="title">
-                <span>Recycle Bin</span>
+                <span>Trash</span>
               </template>
               <a-icon type="delete" />
             </a-tooltip>
diff --git a/zeppelin-web-vue/src/components/Layout/LeftSideBar.vue b/zeppelin-web-vue/src/components/Layout/LeftSideBar.vue
index 8b57aeb..8287e7e 100644
--- a/zeppelin-web-vue/src/components/Layout/LeftSideBar.vue
+++ b/zeppelin-web-vue/src/components/Layout/LeftSideBar.vue
@@ -28,7 +28,7 @@
         v-if="this.$store.state.selectedLeftNavTab === 'trash'"
         class="trash-content"
       >
-        <RecycleBin />
+        <Trash />
       </div>
     </div>
   </div>
@@ -39,7 +39,7 @@ import LeftNavBar from './LeftNavBar.vue'
 import NoteTree from '@/components/Notebook/NoteTree.vue'
 import ActivityConsole from '@/components/ActivityConsole.vue'
 import PackageList from '@/components/Helium/PackageList.vue'
-import RecycleBin from '@/components/Notebook/RecycleBin.vue'
+import Trash from '@/components/Notebook/Trash.vue'
 
 export default {
   name: 'LeftSideBar',
@@ -48,7 +48,7 @@ export default {
     NoteTree,
     ActivityConsole,
     PackageList,
-    RecycleBin
+    Trash
   }
 }
 </script>
diff --git a/zeppelin-web-vue/src/components/Layout/TopMenu.vue b/zeppelin-web-vue/src/components/Layout/TopMenu.vue
index 032a74d..1b83a1f 100644
--- a/zeppelin-web-vue/src/components/Layout/TopMenu.vue
+++ b/zeppelin-web-vue/src/components/Layout/TopMenu.vue
@@ -26,7 +26,7 @@
 
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('save')"
                 href="javascript:void(0)"
               >
@@ -36,7 +36,7 @@
 
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('manage-permissions')"
                 href="javascript:void(0)"
               >
@@ -47,7 +47,7 @@
             <li class="separator"></li>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('export-json')"
                 href="javascript:void(0)"
               >
@@ -56,11 +56,30 @@
             </li>
             <li>
               <a
+                v-if="!isDeleted"
                 v-bind:class="{'disabled': !(isActiveNote)}"
-                @click="executeNoteCommand('delete-temporary')"
+                @click="showMoveToTrashConfirm"
                 href="javascript:void(0)"
               >
-                Move To Recycle Bin
+                Move To Trash
+              </a>
+            </li>
+            <li>
+              <a
+                v-if="isDeleted"
+                @click="executeNoteCommand('restore-note')"
+                href="javascript:void(0)"
+              >
+                Restore
+              </a>
+            </li>
+            <li>
+              <a
+                v-if="isDeleted"
+                @click="showDeletePermConfirm"
+                href="javascript:void(0)"
+              >
+                Delete Permanently
               </a>
             </li>
 
@@ -84,7 +103,7 @@
             <li>
               <a
                 @click="executeNoteCommand('toggle-code')"
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 href="javascript:void(0)"
               >
                 Show/Hide Code
@@ -93,7 +112,7 @@
             <li>
               <a
                 @click="executeNoteCommand('toggle-line-numbers')"
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 href="javascript:void(0)"
               >
                 Show/Hide Line Numbers
@@ -102,7 +121,7 @@
             <li>
               <a
                 @click="executeNoteCommand('toggle-output')"
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 href="javascript:void(0)"
               >
                 Show/Hide Outputs
@@ -111,7 +130,7 @@
             <li class="separator"></li>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('find-and-replace')"
                 href="javascript:void(0)"
               >
@@ -122,7 +141,7 @@
             <li>
               <a
                 @click="showConfirmClearOutput"
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 href="javascript:void(0)"
               >
                 Clear All Outputs
@@ -161,6 +180,16 @@
                 Note Info
               </a>
             </li>
+            <li class="separator"></li>
+            <li>
+              <a
+                v-bind:class="{'disabled': !(isActiveNote)}"
+                @click="executeNoteCommand('reload')"
+                href="javascript:void(0)"
+              >
+                Reload
+              </a>
+            </li>
           </ul>
         </li>
 
@@ -169,7 +198,7 @@
           <ul>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('run-all')"
                 href="javascript:void(0)"
               >
@@ -178,7 +207,7 @@
             </li>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('run-before')"
                 href="javascript:void(0)"
               >
@@ -187,7 +216,7 @@
             </li>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('run-focused')"
                 href="javascript:void(0)"
               >
@@ -196,7 +225,7 @@
             </li>
             <li>
               <a
-                v-bind:class="{'disabled': !(isActiveNote)}"
+                v-bind:class="{'disabled': !(isActiveNote && !isDeleted)}"
                 @click="executeNoteCommand('run-after')"
                 href="javascript:void(0)"
               >
@@ -347,6 +376,11 @@ export default {
     isActiveNote () {
       return (this.$store.state.TabManagerStore.currentTab &&
           this.$store.state.TabManagerStore.currentTab.type === 'note')
+    },
+    isDeleted () {
+      if (!this.isActiveNote) return false
+
+      return this.activeNote.path ? this.activeNote.path.split('/')[1] === this.$root.TRASH_FOLDER_ID
: false
     }
   },
   mounted () {
@@ -382,6 +416,32 @@ export default {
         },
         onCancel () {}
       })
+    },
+    showMoveToTrashConfirm () {
+      let that = this
+      this.$confirm({
+        title: that.$i18n.t('message.note.move_to_trash_confirm'),
+        content: that.$i18n.t('message.note.move_to_trash_content'),
+        onOk () {
+          that.executeNoteCommand('move-to-trash')
+
+          that.$message.success(that.$i18n.t('message.note.move_to_trash_success'), 4)
+        },
+        onCancel () {}
+      })
+    },
+    showDeletePermConfirm () {
+      let that = this
+      this.$confirm({
+        title: that.$i18n.t('message.note.delete_confirm'),
+        content: that.$i18n.t('message.note.delete_content'),
+        onOk () {
+          that.executeNoteCommand('delete-permanently')
+
+          that.$message.success(that.$i18n.t('message.note.delete_success'), 4)
+        },
+        onCancel () {}
+      })
     }
   }
 }
diff --git a/zeppelin-web-vue/src/components/Notebook/Controls.vue b/zeppelin-web-vue/src/components/Notebook/Controls.vue
index f3f09a8..7b3b45f 100644
--- a/zeppelin-web-vue/src/components/Notebook/Controls.vue
+++ b/zeppelin-web-vue/src/components/Notebook/Controls.vue
@@ -4,6 +4,7 @@
       <a
         href="javascript: void(0);"
         @click="executeNoteCommand('run-all')"
+        :disabled="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
@@ -16,6 +17,7 @@
       <a
         href="javascript: void(0);"
         @click="executeNoteCommand('save')"
+        :disabled="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
@@ -28,6 +30,7 @@
       <a
         href="javascript: void(0);"
         @click="executeNoteCommand('show-clone')"
+        :disabled="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
@@ -40,6 +43,7 @@
       <a
         href="javascript: void(0);"
         @click="executeNoteCommand('export-json')"
+        :disabled="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
@@ -51,53 +55,74 @@
 
       <a
         href="javascript: void(0);"
-        @click="showDeleteConfirm"
+        @click="showMoveToTrashConfirm"
+        v-if="!isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
-            <span>Delete</span>
+            <span>Move to Trash</span>
           </template>
           <a-icon type="delete" />
         </a-tooltip>
       </a>
-    </div>
 
-    <div class="right-controls">
       <a
         href="javascript: void(0);"
+        @click="executeNoteCommand('restore-note')"
+        v-if="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
-            <span>Search Code</span>
+            <span>Restore</span>
           </template>
-          <a-icon type="file-search" />
+          <a-icon type="reload" />
         </a-tooltip>
       </a>
 
       <a
         href="javascript: void(0);"
+        @click="showDeletePermConfirm"
+        v-if="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
-            <span>Version Control</span>
+            <span>Delete Permanently</span>
           </template>
-          <a-icon type="diff" />
+          <a-icon type="delete" />
         </a-tooltip>
       </a>
+    </div>
 
+    <div class="right-controls">
       <a
         href="javascript: void(0);"
-        @click="executeNoteCommand('reload')"
+        :disabled="isDeleted"
       >
         <a-tooltip placement="top">
           <template slot="title">
-            <span>Reload</span>
+            <span>Search Code</span>
           </template>
-          <a-icon type="reload" />
+          <a-icon type="file-search" />
         </a-tooltip>
       </a>
 
-      <a-dropdown :trigger="['click']">
+      <a
+        href="javascript: void(0);"
+        :disabled="isDeleted"
+      >
+        <a-tooltip placement="top">
+          <template slot="title">
+            <span>Version Control</span>
+          </template>
+          <a-icon type="diff" />
+        </a-tooltip>
+      </a>
+
+      <a-dropdown
+        :disabled="isDeleted"
+        :class="{disabled: isDeleted}"
+        :trigger="['click']"
+      >
         <a class="ant-dropdown-link" href="#">
           <span> Default </span>
           <a-icon type="down" />
@@ -124,19 +149,38 @@ export default {
   props: {
     noteId: { required: true }
   },
+  computed: {
+    isDeleted () {
+      let notePath = this.$store.getters.getNote(this.$props.noteId).path
+      return notePath ? notePath.split('/')[1] === this.$root.TRASH_FOLDER_ID : false
+    }
+  },
   methods: {
-    executeNoteCommand (command) {
-      this.$root.executeCommand('note', command)
+    executeNoteCommand (command, args) {
+      this.$root.executeCommand('note', command, args)
+    },
+    showMoveToTrashConfirm () {
+      let that = this
+      this.$confirm({
+        title: that.$i18n.t('message.note.move_to_trash_confirm'),
+        content: that.$i18n.t('message.note.move_to_trash_content'),
+        onOk () {
+          that.executeNoteCommand('move-to-trash')
+
+          that.$message.success(that.$i18n.t('message.note.move_to_trash_success'), 4)
+        },
+        onCancel () {}
+      })
     },
-    showDeleteConfirm () {
+    showDeletePermConfirm () {
       let that = this
       this.$confirm({
-        title: that.$i18n.t('message.note.move_to_rb_confirm'),
-        content: that.$i18n.t('message.note.move_to_rb_content'),
+        title: that.$i18n.t('message.note.delete_confirm'),
+        content: that.$i18n.t('message.note.delete_content'),
         onOk () {
-          that.executeNoteCommand('delete-temporary')
+          that.executeNoteCommand('delete-permanently')
 
-          that.$message.success(that.$i18n.t('message.note.move_to_rb_success'), 4)
+          that.$message.success(that.$i18n.t('message.note.delete_success'), 4)
         },
         onCancel () {}
       })
@@ -153,11 +197,17 @@ export default {
   a {
     display: inline-block;
     height: 100%;
-    padding: 2px 6px;
+    padding: 2px 7px;
 
     span {
       vertical-align: middle;
     }
+
+    &.disabled {
+      cursor: default;
+      color: inherit;
+      opacity: 0.5;
+    }
   }
 
   .left-controls {
diff --git a/zeppelin-web-vue/src/components/Notebook/NoteTree.vue b/zeppelin-web-vue/src/components/Notebook/NoteTree.vue
index 0e8dacd..01b1544 100644
--- a/zeppelin-web-vue/src/components/Notebook/NoteTree.vue
+++ b/zeppelin-web-vue/src/components/Notebook/NoteTree.vue
@@ -14,6 +14,13 @@
       </div>
     </div>
 
+    <a-input-search
+      v-if="!isLoading"
+      placeholder="search notebooks"
+      class="search-notebook-box"
+      @search="onSearch"
+    />
+
     <ul>
       <li
         v-for="(note, index) in this.notes"
@@ -25,10 +32,46 @@
           v-bind:title="note.path"
           class="text-ellipsis"
           :class="{'active':  note.id === activeNoteId}"
-          v-on:click="openNote(note)"
+          @click="openNote(note)"
         >
           <a-icon type="file" />
-          {{ getFileName(note.path) }}
+          <span>{{ getFileName(note.path) }}</span>
+
+          <a-dropdown
+            class="note-menu"
+            placement="bottomRight"
+          >
+            <a class="ant-dropdown-link" href="#">
+              <a-icon type="ellipsis" />
+            </a>
+            <a-menu slot="overlay">
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="openNote(note)"
+                >
+                  Open Notebook
+                </a>
+              </a-menu-item>
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="showRenameDialog(note)"
+                >
+                  Rename
+                </a>
+              </a-menu-item>
+              <a-menu-divider />
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="showMoveToTrashConfirm(note.id)"
+                >
+                  Move to Trash
+                </a>
+              </a-menu-item>
+            </a-menu>
+          </a-dropdown>
         </a>
       </li>
     </ul>
@@ -40,6 +83,11 @@ import ws from '@/services/ws-helper'
 
 export default {
   name: 'NoteTree',
+  data () {
+    return {
+
+    }
+  },
   mounted () {
     ws.getConn().send({ op: 'LIST_NOTES' })
   },
@@ -63,6 +111,31 @@ export default {
     },
     getFileName (path) {
       return path.substr(path.lastIndexOf('/') + 1)
+    },
+    onSelect (keys) {
+      console.log('Trigger Select', keys)
+    },
+    onSearch (value) {
+      // a
+    },
+    showMoveToTrashConfirm (noteId) {
+      let that = this
+      this.$confirm({
+        title: that.$i18n.t('message.note.move_to_trash_confirm'),
+        content: that.$i18n.t('message.note.move_to_trash_content'),
+        onOk () {
+          that.$root.executeCommand('note', 'move-to-trash', noteId)
+
+          that.$message.success(that.$i18n.t('message.note.move_to_trash_success'), 4)
+        },
+        onCancel () {}
+      })
+    },
+    showRenameDialog (note) {
+      this.$root.executeCommand('showRenameNoteDialog', {
+        id: note.id,
+        path: note.path
+      })
     }
   }
 }
@@ -82,6 +155,11 @@ export default {
   }
 }
 
+.search-notebook-box {
+  padding: 8px 6px;
+  width: calc(100%);
+}
+
 .notes {
   list-style: none;
   margin: 0;
@@ -92,13 +170,24 @@ export default {
     margin: 0;
     padding: 0;
 
-    li {
+    li.note {
+      position: relative;
+
       a {
         font-size: 14px;
         padding: 5px 10px;
-        display: block;
+        display: flex;
         border-left: 4px solid transparent;
 
+        i {
+          line-height: 20px;
+        }
+
+        &> span {
+          position: relative;
+          padding-left: 5px;
+        }
+
         &.active {
           background: #f1eeee;
           border-left-color: #2f71a9;
@@ -106,7 +195,17 @@ export default {
 
         &:hover {
           background: #F1F1F1;
+        }
+      }
+
+      a.note-menu {
+        position: absolute;
+        top: 5px;
+        right: 5px;
+        padding: 0;
 
+        i {
+          transform: rotate(90deg);
         }
       }
     }
diff --git a/zeppelin-web-vue/src/components/Notebook/RecycleBin.vue b/zeppelin-web-vue/src/components/Notebook/RecycleBin.vue
deleted file mode 100644
index 10dc920..0000000
--- a/zeppelin-web-vue/src/components/Notebook/RecycleBin.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<template>
-  <div class="notes">
-    <div
-      v-if="isLoading"
-    >
-      <div
-        v-for="index in 3"
-        :key="index"
-        class="timeline-item"
-      >
-        <div class="animated-background">
-          <div class="background-masker nb-label-separator"></div>
-        </div>
-      </div>
-    </div>
-
-    <ul>
-      <li
-        v-for="(note, index) in this.notes"
-        :key="index"
-        class="note"
-      >
-        <a
-          href="javascript: void(0);"
-          v-bind:title="note.path"
-          class="text-ellipsis"
-          :class="{'active':  note.id === activeNoteId}"
-          v-on:click="openNote(note)"
-        >
-          <a-icon type="file" />
-          {{ getFileName(note.path) }}
-        </a>
-      </li>
-    </ul>
-  </div>
-</template>
-
-<script>
-export default {
-  name: 'RecycleBin',
-  computed: {
-    isLoading () {
-      return this.$store.state.NotebookStore.isListLoading
-    },
-    activeNoteId () {
-      return this.$store.state.TabManagerStore.currentTab && this.$store.state.TabManagerStore.currentTab.id
-    },
-    notes () {
-      return this.$store.state.NotebookStore.notes.filter(n => (n.path ? n.path.split('/')[1]
=== this.$root.TRASH_FOLDER_ID : false))
-    }
-  },
-  methods: {
-    openNote (note) {
-      this.$root.executeCommand('tabs', 'open', {
-        type: 'note',
-        note: note
-      })
-    },
-    getFileName (path) {
-      return path.substr(path.lastIndexOf('/') + 1)
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.timeline-item {
-  padding: 0 12px;
-  margin: 9px auto;
-  height: 20px;
-
-  .nb-label-separator {
-    left: 20px;
-    top: 0;
-    width: 4px;
-    height: 24px;
-  }
-}
-
-.notes {
-  list-style: none;
-  margin: 0;
-  padding: 0;
-
-  ul {
-    list-style: none;
-    margin: 0;
-    padding: 0;
-
-    li {
-      a {
-        font-size: 14px;
-        padding: 5px 10px;
-        display: block;
-        border-left: 4px solid transparent;
-
-        &.active {
-          background: #f1eeee;
-          border-left-color: #2f71a9;
-        }
-
-        &:hover {
-          background: #F1F1F1;
-
-        }
-      }
-    }
-  }
-}
-</style>
diff --git a/zeppelin-web-vue/src/components/Notebook/Rename.vue b/zeppelin-web-vue/src/components/Notebook/Rename.vue
new file mode 100644
index 0000000..cc72214
--- /dev/null
+++ b/zeppelin-web-vue/src/components/Notebook/Rename.vue
@@ -0,0 +1,91 @@
+<template>
+  <div>
+    <a-modal
+        v-model="showDialog"
+        title="Rename Note"
+        onOk="handleOk"
+        :maskClosable="false"
+      >
+      <template slot="footer">
+        <a-button key="back" @click="handleCancel">Cancel</a-button>
+        <a-button key="submit" type="primary" :loading="loading" @click="handleOk">
+          Rename
+        </a-button>
+      </template>
+
+      <a-form layout="vertical">
+        <a-form-item
+          label="Note Name"
+        >
+          <a-input placeholder="Enter Note Name"  v-model="name"/>
+        </a-form-item>
+
+        <a-alert message="Use '/' to create folders. Example: /NoteDirA/Note1" type="info"
/>
+
+        <input type="hidden" v-model="sourceNoteId" name="sourceNoteId" value="" />
+
+      </a-form>
+    </a-modal>
+  </div>
+</template>
+
+<script>
+import { EventBus } from '@/services/event-bus'
+
+export default {
+  name: 'RenameNote',
+  data () {
+    return {
+      showDialog: false,
+      loading: false,
+
+      name: '',
+      sourceNoteId: ''
+    }
+  },
+  computed: {
+    interpreters () {
+      return this.$store.state.InterpreterStore.interpreters
+    }
+  },
+  mounted () {
+    EventBus.$on('showRenameNoteDialog', (note) => {
+      this.sourceNoteId = note.id
+      this.name = note.path
+      this.showDialog = true
+    })
+  },
+  methods: {
+    handleOk (e) {
+      this.loading = true
+
+      this.$root.executeCommand('note', 'rename', {
+        newNoteName: this.name,
+        sourceNoteId: this.sourceNoteId
+      })
+
+      let that = this
+      setTimeout(() => {
+        this.showDialog = false
+        this.loading = false
+
+        this.resetForm()
+
+        that.$message.success(that.$i18n.t('message.note.rename_success'), 4)
+        // Pending - validation
+        // Pending update everywhere
+      }, 1000)
+    },
+    handleCancel (e) {
+      this.showDialog = false
+    },
+    resetForm () {
+      this.name = ''
+      this.sourceNoteId = ''
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+</style>
diff --git a/zeppelin-web-vue/src/components/Notebook/Trash.vue b/zeppelin-web-vue/src/components/Notebook/Trash.vue
new file mode 100644
index 0000000..9d59cb4
--- /dev/null
+++ b/zeppelin-web-vue/src/components/Notebook/Trash.vue
@@ -0,0 +1,190 @@
+<template>
+  <div class="notes">
+    <div
+      v-if="isLoading"
+    >
+      <div
+        v-for="index in 3"
+        :key="index"
+        class="timeline-item"
+      >
+        <div class="animated-background">
+          <div class="background-masker nb-label-separator"></div>
+        </div>
+      </div>
+    </div>
+
+    <ul>
+      <li
+        v-for="(note, index) in this.notes"
+        :key="index"
+        class="note"
+      >
+        <a
+          href="javascript: void(0);"
+          v-bind:title="note.path"
+          class="text-ellipsis"
+          :class="{'active':  note.id === activeNoteId}"
+          @click="openNote(note)"
+        >
+          <a-icon type="file" />
+          <span>{{ getFileName(note.path) }}</span>
+
+          <a-dropdown
+            class="note-menu"
+            placement="bottomRight"
+          >
+            <a class="ant-dropdown-link" href="#">
+              <a-icon type="ellipsis" />
+            </a>
+            <a-menu slot="overlay">
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="openNote(note)"
+                >
+                  Open Notebook
+                </a>
+              </a-menu-item>
+              <a-menu-divider />
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="executeNoteCommand('restore-note', note.id)"
+                >
+                  Restore
+                </a>
+              </a-menu-item>
+              <a-menu-item>
+                <a
+                  href="javascript: void(0);"
+                  @click="showDeletePermConfirm(note.id)"
+                >
+                  Delete Permanently
+                </a>
+              </a-menu-item>
+            </a-menu>
+          </a-dropdown>
+        </a>
+      </li>
+    </ul>
+
+    <div
+      v-if="this.notes.length === 0"
+      class="pt-2 pl-2"
+    >
+      {{ $t("message.note.empty_trash") }}
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Trash',
+  computed: {
+    isLoading () {
+      return this.$store.state.NotebookStore.isListLoading
+    },
+    activeNoteId () {
+      return this.$store.state.TabManagerStore.currentTab && this.$store.state.TabManagerStore.currentTab.id
+    },
+    notes () {
+      return this.$store.state.NotebookStore.notes.filter(n => (n.path ? n.path.split('/')[1]
=== this.$root.TRASH_FOLDER_ID : false))
+    }
+  },
+  methods: {
+    executeNoteCommand (command, args) {
+      this.$root.executeCommand('note', command, args)
+    },
+    openNote (note) {
+      this.$root.executeCommand('tabs', 'open', {
+        type: 'note',
+        note: note
+      })
+    },
+    showDeletePermConfirm (noteId) {
+      let that = this
+      this.$confirm({
+        title: that.$i18n.t('message.note.delete_confirm'),
+        content: that.$i18n.t('message.note.delete_content'),
+        onOk () {
+          that.executeNoteCommand('delete-permanently', noteId)
+
+          that.$message.success(that.$i18n.t('message.note.delete_success'), 4)
+        },
+        onCancel () {}
+      })
+    },
+    getFileName (path) {
+      return path.substr(path.lastIndexOf('/') + 1)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.timeline-item {
+  padding: 0 12px;
+  margin: 9px auto;
+  height: 20px;
+
+  .nb-label-separator {
+    left: 20px;
+    top: 0;
+    width: 4px;
+    height: 24px;
+  }
+}
+
+.notes {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+
+  ul {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+
+    li.note {
+      position: relative;
+
+      a {
+        font-size: 14px;
+        padding: 5px 10px;
+        display: flex;
+        border-left: 4px solid transparent;
+
+        i {
+          line-height: 20px;
+        }
+
+        &> span {
+          position: relative;
+          padding-left: 5px;
+        }
+
+        &.active {
+          background: #f1eeee;
+          border-left-color: #2f71a9;
+        }
+
+        &:hover {
+          background: #F1F1F1;
+        }
+      }
+
+      a.note-menu {
+        position: absolute;
+        top: 5px;
+        right: 5px;
+        padding: 0;
+
+        i {
+          transform: rotate(90deg);
+        }
+      }
+    }
+  }
+}
+</style>
diff --git a/zeppelin-web-vue/src/i18n.js b/zeppelin-web-vue/src/i18n.js
index 3e2424f..92ce8fb 100644
--- a/zeppelin-web-vue/src/i18n.js
+++ b/zeppelin-web-vue/src/i18n.js
@@ -21,9 +21,14 @@ export const i18n = new VueI18n({
           clone_success: 'Note cloned successfully.',
           clear_output_confirm: 'Do you want to clear the ouput for all the paragraphs?',
           clear_output_success: 'Output cleared successfully for all the paragraphs.',
-          move_to_rb_confirm: 'Do you want to delete this Note?',
-          move_to_rb_content: 'This will move the note to Recycle Bin and you can still recover
it.',
-          move_to_rb_success: 'Note moved to recycle bin successfully.'
+          move_to_trash_confirm: 'Do you want to delete this Note?',
+          move_to_trash_content: 'This will move the note to Trash and you can still recover
it.',
+          move_to_trash_success: 'Note moved to Trash successfully.',
+          delete_confirm: 'Do you want to delete the notebook permanently?',
+          delete_content: 'This will remove it permanently and can not be recovered.',
+          delete_success: 'Note deleted successfully.',
+          rename_success: 'Note renamed successfully',
+          empty_trash: 'Empty Trash'
         }
       }
     },
diff --git a/zeppelin-web-vue/src/mixins/array_utils.js b/zeppelin-web-vue/src/mixins/array_utils.js
new file mode 100644
index 0000000..a7932b3
--- /dev/null
+++ b/zeppelin-web-vue/src/mixins/array_utils.js
@@ -0,0 +1,8 @@
+export default {
+  mergeArray (a, b, prop) {
+    return b.map((itemb) => {
+      let srcItem = a.find(itema => itema[prop] === itemb[prop])
+      return srcItem? { ...srcItem, ...itemb } : itemb
+    })
+  }
+}
diff --git a/zeppelin-web-vue/src/services/command-manager.js b/zeppelin-web-vue/src/services/command-manager.js
index f271132..edd22a2 100644
--- a/zeppelin-web-vue/src/services/command-manager.js
+++ b/zeppelin-web-vue/src/services/command-manager.js
@@ -52,7 +52,10 @@ export default {
       let isActiveNote = (store.state.TabManagerStore.currentTab &&
                               store.state.TabManagerStore.currentTab.type === 'note')
 
-      if (!(isActiveNote || ['show-create', 'create', 'show-import', 'import-json'].indexOf(command)
!== -1)) {
+      if (!(isActiveNote || ['show-create', 'create', 'rename', 'show-import', 'import-json',
+                              'move-to-trash', 'restore-note', 'delete-permanently'].indexOf(command)
!== -1)
+        )
+      {
         return
       }
       let note = store.state.TabManagerStore.currentTab
@@ -70,6 +73,9 @@ export default {
         case 'import-json':
           notebookUtils.importJSON(args)
           break
+        case 'rename':
+          notebookUtils.rename(args)
+          break
         case 'clear-output':
           notebookUtils.clearAllOutputs(note.id)
           break
@@ -89,13 +95,14 @@ export default {
           break
         case 'print':
           break
-        case 'delete-temporary':
-          notebookUtils.deleteTemporary(note.id)
+        case 'move-to-trash':
+          notebookUtils.moveToTrash(args || (note && note.id))
           break
         case 'restore-note':
-          notebookUtils.restore(note.id)
+          notebookUtils.restore(args || (note && note.id))
           break
         case 'delete-permanently':
+        notebookUtils.deletePermanently(args || (note && note.id))
           break
         case 'show-clone':
           notebookUtils.showCloneModal(note.id)
diff --git a/zeppelin-web-vue/src/services/notebook-utils.js b/zeppelin-web-vue/src/services/notebook-utils.js
index 9e2bb41..85e0c1c 100644
--- a/zeppelin-web-vue/src/services/notebook-utils.js
+++ b/zeppelin-web-vue/src/services/notebook-utils.js
@@ -44,6 +44,19 @@ export default {
     // Pending - open the note after create
   },
 
+  rename (params) {
+    console.log(params)
+    wsHelper.getConn().send({
+      op: 'NOTE_RENAME',
+      data: {
+        id: params.sourceNoteId,
+        name: params.newNoteName
+      }
+    })
+
+    // Reload the left sidebar will happen automatically as it will return the full list
as the response
+  },
+
   open (note) {
     wsFactory.initNoteConnection(note.id, this.store)
 
@@ -111,7 +124,7 @@ export default {
     })
   },
 
-  deleteTemporary (noteId) {
+  moveToTrash (noteId) {
     wsHelper.getConn().send({
       op: 'MOVE_NOTE_TO_TRASH',
       data: {
@@ -120,10 +133,10 @@ export default {
     })
 
     // Remove the tab
-    this.store.dispatch('removeTab', this.store.state.TabManagerStore.currentTab)
-
-    // Reload the note list
-    this.reloadList()
+    let currentTab = this.store.state.TabManagerStore.currentTab
+    if (currentTab && currentTab.id === noteId) {
+      this.store.dispatch('removeTab', this.store.state.TabManagerStore.currentTab)
+    }
   },
 
   deletePermanently (noteId) {
@@ -133,9 +146,6 @@ export default {
         id: noteId
       }
     })
-
-    // Reload the note list
-    this.reloadList()
   },
 
   restore (noteId) {
@@ -145,8 +155,5 @@ export default {
         id: noteId
       }
     })
-
-    // Reload the note list
-    this.reloadList()
   }
 }
diff --git a/zeppelin-web-vue/src/stores/notebook_store.js b/zeppelin-web-vue/src/stores/notebook_store.js
index 5cebad1..616a7f4 100644
--- a/zeppelin-web-vue/src/stores/notebook_store.js
+++ b/zeppelin-web-vue/src/stores/notebook_store.js
@@ -1,4 +1,5 @@
 import Vue from 'vue'
+import arrayUtils from '@/mixins/array_utils.js'
 
 export default {
   state: {
@@ -66,7 +67,7 @@ export default {
     },
     mutateNotes (state, data) {
       state.isListLoading = false
-      state.notes = data.notes
+      state.notes = arrayUtils.mergeArray(state.notes, data.notes, 'id')
     },
     mutateNote (state, noteObj) {
       let index = state.notes.map(function (n) { return n.id }).indexOf(noteObj.note.id)
@@ -163,7 +164,7 @@ export default {
     setNavBar (context, data) {
       context.commit('mutateNavBar', data)
     },
-    setNoteMenu (context, data) {
+    setNoteList (context, data) {
       context.commit('mutateNotes', data)
     },
     setNoteContent (context, data) {
diff --git a/zeppelin-web-vue/src/stores/tab_manager_store.js b/zeppelin-web-vue/src/stores/tab_manager_store.js
index 39731f1..4abf6c4 100644
--- a/zeppelin-web-vue/src/stores/tab_manager_store.js
+++ b/zeppelin-web-vue/src/stores/tab_manager_store.js
@@ -13,11 +13,11 @@ export default {
   },
   mutations: {
     addTab (state, data) {
-      let isExist = state.tabs.filter(t => (t.path && t.path === data.path) ||
(!t.path && t.type === data.type))
-      state.currentTab = data
-      if (isExist.length === 0) {
+      let filteredTab = state.tabs.filter(t => (t.path && t.path === data.path)
|| (!t.path && t.type === data.type))
+      if (filteredTab.length === 0) {
         state.tabs.push(data)
       }
+      state.currentTab = state.tabs[state.tabs.length - 1]
       return state
     },
     removeTab (state, data) {


Mime
View raw message