From f8443b4c8d0b4ad3d9409f124c06aaadc379af8e Mon Sep 17 00:00:00 2001
From: Sonia Zorba <sonia.zorba@inaf.it>
Date: Mon, 19 Aug 2019 12:49:42 +0200
Subject: [PATCH] FE: Changes post BE-refactoring; Added loading animation and
 small improvements

---
 gms-ui/.env.production                     |  2 +-
 gms-ui/src/App.vue                         | 45 +++++++++++++++++++---
 gms-ui/src/api/server/index.js             | 36 ++++++++++++++---
 gms-ui/src/components/GroupsBreadcrumb.vue | 17 ++++++--
 gms-ui/src/components/GroupsPanel.vue      |  6 ++-
 gms-ui/src/components/Main.vue             | 23 +++++++++--
 gms-ui/src/main.js                         |  4 +-
 gms-ui/src/plugins/bootstrap-vue.js        |  1 -
 gms-ui/src/store.js                        | 38 ++++++++++++------
 gms-ui/vue.config.js                       |  2 +-
 10 files changed, 138 insertions(+), 36 deletions(-)

diff --git a/gms-ui/.env.production b/gms-ui/.env.production
index e86ff0c..384270e 100644
--- a/gms-ui/.env.production
+++ b/gms-ui/.env.production
@@ -1,2 +1,2 @@
 VUE_APP_API_CLIENT = 'server'
-BASE_API_URL = 'http://localhost:8081/'
+VUE_APP_API_BASE_URL = 'http://localhost:8081/'
diff --git a/gms-ui/src/App.vue b/gms-ui/src/App.vue
index ed82340..98a8241 100644
--- a/gms-ui/src/App.vue
+++ b/gms-ui/src/App.vue
@@ -4,6 +4,11 @@
     <div class="container">
       <Main />
     </div>
+    <div id="loading" v-if="loading">
+      <div id="spinner-wrapper">
+        <font-awesome-icon icon="spinner" spin />
+      </div>
+    </div>
   </div>
 </template>
 
@@ -11,7 +16,7 @@
 import TopMenu from './components/TopMenu.vue';
 import Main from './components/Main.vue';
 import { mapState } from 'vuex';
-//import { ToastPlugin } from 'bootstrap-vue';
+import client from 'api-client';
 
 export default {
   name: 'app',
@@ -20,11 +25,11 @@ export default {
     Main
   },
   computed: mapState({
-    model: state => state.model
+    model: state => state.model,
+    input: state => state.input,
+    loading: state => state.loading
   }),
   mounted: function() {
-    this.$store.commit('fetchGroupsModel');
-
     var self = this;
     document.addEventListener('apiError', function (event) {
       self.$bvToast.toast(event.message, {
@@ -32,12 +37,21 @@ export default {
         variant: 'danger',
         solid: true
       });
-    })
+    });
+    document.addEventListener('loading', function (event) {
+      self.$store.commit('setLoading', event.value);
+    });
+
+    // retrieve the initial model
+    client.fetchHomePageModel(this.input)
+      .then(model => {
+        this.$store.commit('updateHomePageModel', model);
+      });
   }
 }
 </script>
 
-<style>
+<style scoped>
 #app {
   font-family: 'Avenir', Helvetica, Arial, sans-serif;
   -webkit-font-smoothing: antialiased;
@@ -45,4 +59,23 @@ export default {
   text-align: center;
   color: #2c3e50;
 }
+
+#loading {
+  font-size: 40px;
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  right: 0;
+  left: 0;
+  background-color: rgba(255, 255, 255, 0.7);
+  z-index: 1000;
+}
+
+#spinner-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  width: 100%;
+}
 </style>
diff --git a/gms-ui/src/api/server/index.js b/gms-ui/src/api/server/index.js
index 0e883d9..4634401 100644
--- a/gms-ui/src/api/server/index.js
+++ b/gms-ui/src/api/server/index.js
@@ -1,9 +1,11 @@
-const BASE_API_URL = "http://localhost:8081/"
+const BASE_API_URL = process.env.VUE_APP_API_BASE_URL;
 
 function apiRequest(url, data) {
+  loading(true);
   return new Promise((resolve, reject) => {
     fetch(url, data)
       .then(response => {
+        loading(false);
         if([200, 201, 204, 400].includes(response.status)) { // valid status codes
           resolve(response.json());
         } else {
@@ -11,6 +13,7 @@ function apiRequest(url, data) {
         }
       })
       .catch(error => {
+        loading(false);
         dispatchApiErrorEvent(error);
       });
   });
@@ -29,14 +32,35 @@ function dispatchApiErrorEvent(error) {
   document.dispatchEvent(event);
 }
 
+/* For loading animation */
+function loading(value) {
+  let event = new CustomEvent('loading');
+  event.value = value;
+  document.dispatchEvent(event);
+}
+
 export default {
-  fetchGroupsModel (input) {
+  fetchHomePageModel (input) {
+    let url = BASE_API_URL
+            + 'home?groupId=' + input.selectedGroupId
+            + '&paginatorPageSize=' + input.paginatorPageSize
+            + '&paginatorPage=' + input.paginatorPage;
+    return apiRequest(url, {
+      method: 'GET',
+      cache: 'no-cache',
+      credentials: 'include',
+      headers: {
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+      }
+    });
+  },
+  fetchPanel (input) {
     let url = BASE_API_URL
-            + 'groups?groupId=' + input.selectedGroupId
-            + '&tab=' + input.selectedTab
+            + input.selectedTab
+            + '?groupId=' + input.selectedGroupId
             + '&paginatorPageSize=' + input.paginatorPageSize
-            + '&paginatorPage=' + input.paginatorPage
-            + '&page=' + input.page;
+            + '&paginatorPage=' + input.paginatorPage;
     return apiRequest(url, {
       method: 'GET',
       cache: 'no-cache',
diff --git a/gms-ui/src/components/GroupsBreadcrumb.vue b/gms-ui/src/components/GroupsBreadcrumb.vue
index b245877..ea10fc6 100644
--- a/gms-ui/src/components/GroupsBreadcrumb.vue
+++ b/gms-ui/src/components/GroupsBreadcrumb.vue
@@ -11,6 +11,7 @@
 
 <script>
 import { mapState } from 'vuex';
+import client from 'api-client';
 
 function buildItems(values) {
   let groups = [];
@@ -22,7 +23,9 @@ function buildItems(values) {
   }
 
   // Activate the last item
-  groups[groups.length - 1].active = true;
+  if(groups.length > 0) {
+    groups[groups.length - 1].active = true;
+  }
 
   return groups;
 }
@@ -31,12 +34,20 @@ export default {
   name: 'GroupsBreadcrumb',
   computed: mapState({
     model: state => state.model,
+    input: state => state.input,
     groups: state => buildItems(state.model.breadcrumbs)
   }),
   methods: {
     changeBreadcrumb: function(groupId) {
-      this.$store.state.input.selectedGroupId = groupId;
-      this.$store.commit('fetchGroupsModel');
+      this.input.selectedGroupId = groupId;
+      if(this.input.selectedTab === 'groups') {
+        client.fetchPanel(this.input)
+          .then(model => {
+            this.$store.commit('updateGroupsPanel', model);
+          });
+      } else {
+        this.$store.commit('setTabIndex', 0);
+      }
     }
   }
 }
diff --git a/gms-ui/src/components/GroupsPanel.vue b/gms-ui/src/components/GroupsPanel.vue
index 04b55fc..cdc739a 100644
--- a/gms-ui/src/components/GroupsPanel.vue
+++ b/gms-ui/src/components/GroupsPanel.vue
@@ -56,6 +56,7 @@
 import RenameGroupModal from './modals/RenameGroupModal.vue';
 import ConfirmRemoveGroupModal from './modals/ConfirmRemoveGroupModal.vue';
 import { mapState, mapActions } from 'vuex';
+import client from 'api-client';
 
 export default {
   name: 'GroupsPanel',
@@ -80,7 +81,10 @@ export default {
   methods: {
     openGroup: function(group) {
       this.$store.state.input.selectedGroupId = group.groupId;
-      this.$store.commit('fetchGroupsModel');
+      client.fetchPanel(this.input)
+        .then(model => {
+          this.$store.commit('updateGroupsPanel', model);
+        });
     },
     openRenameGroupModal: function(group) {
       this.$refs.renameGroupModal.openRenameGroupModal(group);
diff --git a/gms-ui/src/components/Main.vue b/gms-ui/src/components/Main.vue
index 27862fd..1e9a6f1 100644
--- a/gms-ui/src/components/Main.vue
+++ b/gms-ui/src/components/Main.vue
@@ -2,7 +2,7 @@
   <div>
     <GroupsBreadcrumb />
     <b-card no-body>
-      <b-tabs content-class="mt-3" card v-on:input="tabChanged">
+      <b-tabs content-class="mt-3" card v-on:input="tabChanged" v-model="input.tabIndex">
         <GroupsPanel />
         <MembersPanel />
         <PermissionsPanel />
@@ -29,6 +29,7 @@ import AddGroupModal from './modals/AddGroupModal.vue'
 import AddMemberModal from './modals/AddMemberModal.vue'
 import AddPermissionModal from './modals/AddPermissionModal.vue'
 import { mapState } from 'vuex';
+import client from 'api-client';
 
 export default {
   name: 'Main',
@@ -43,6 +44,7 @@ export default {
   },
   computed: mapState({
     model: state => state.model,
+    input: state => state.input,
     showAddMemberBtn: state => state.model.permission === 'ADMIN' && state.input.selectedTab === 'members',
     showAddCollaboratorBtn: state => state.model.permission === 'MANAGE_MEMBERS' && state.input.selectedTab === 'members',
     showAddGroupBtn: state => state.model.permission === 'ADMIN' && state.input.selectedTab === 'groups',
@@ -62,9 +64,22 @@ export default {
           tab = 'permissions';
           break;
       }
-      if(this.$store.state.input.selectedTab !== tab) {
-        this.$store.state.input.selectedTab = tab;
-        this.$store.commit('fetchGroupsModel');
+      if(this.input.selectedTab !== tab) {
+        this.input.selectedTab = tab;
+        client.fetchPanel(this.input)
+          .then(model => {
+            switch(this.input.selectedTab) {
+              case 'groups':
+                this.$store.commit('updateGroupsPanel', model);
+                break;
+              case 'members':
+                this.$store.commit('updateMembersPanel', model);
+                break;
+              case 'permissions':
+                this.$store.commit('updatePermissionsPanel', model);
+                break;
+            }
+          });
       }
     },
     openAddGroupModal: function() {
diff --git a/gms-ui/src/main.js b/gms-ui/src/main.js
index 9fb81c6..1d6dc7e 100644
--- a/gms-ui/src/main.js
+++ b/gms-ui/src/main.js
@@ -5,10 +5,10 @@ import store from './store.js'
 import './plugins/bootstrap-vue'
 import App from './App.vue'
 import { library } from '@fortawesome/fontawesome-svg-core'
-import { faTrash, faEdit } from '@fortawesome/free-solid-svg-icons'
+import { faTrash, faEdit, faSpinner } from '@fortawesome/free-solid-svg-icons'
 import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
 
-library.add(faTrash, faEdit);
+library.add(faTrash, faEdit, faSpinner);
 
 Vue.component('font-awesome-icon', FontAwesomeIcon);
 
diff --git a/gms-ui/src/plugins/bootstrap-vue.js b/gms-ui/src/plugins/bootstrap-vue.js
index c29a066..b9d414f 100644
--- a/gms-ui/src/plugins/bootstrap-vue.js
+++ b/gms-ui/src/plugins/bootstrap-vue.js
@@ -5,4 +5,3 @@ import 'bootstrap/dist/css/bootstrap.min.css';
 import 'bootstrap-vue/dist/bootstrap-vue.css';
 
 Vue.use(BootstrapVue);
-//Vue.use(ToastPlugin);
diff --git a/gms-ui/src/store.js b/gms-ui/src/store.js
index ddfed3b..48e0677 100644
--- a/gms-ui/src/store.js
+++ b/gms-ui/src/store.js
@@ -2,38 +2,54 @@
 
 import Vue from 'vue';
 import Vuex from 'vuex';
-import client from 'api-client';
 
 Vue.use(Vuex);
 
 export default new Vuex.Store({
   state: {
     // values populated from API calls
-    model: null,
+    model: {
+      breadcrumbs: [],
+      groupsPanel: null,
+      permissionsPanel: null,
+      membersPanel: null,
+      permission: null,
+      user: null
+    },
     // values used to perform API calls
     input: {
       selectedGroupId: 'ROOT',
       paginatorPageSize: 20,
       paginatorPage: 1,
       selectedTab: 'groups',
-      page: 'main'
-    }
+      tabIndex: 0
+    },
+    loading: false
   },
   mutations: {
-    fetchGroupsModel(state) {
-      client.fetchGroupsModel(this.state.input)
-        .then(model => {
-          this.state.model = model;
-        });
+    updateHomePageModel(state, model) {
+      this.state.model.breadcrumbs = model.breadcrumbs;
+      this.state.model.groupsPanel = model.groupsPanel;
+      this.state.model.permission = model.permission;
+      this.state.model.user = model.user;
     },
-    updateGroupsPanel(state, groupsPanel) {
-      this.state.model.groupsPanel = groupsPanel;
+    updateGroupsPanel(state, model) {
+      this.state.model.breadcrumbs = model.breadcrumbs;
+      this.state.model.groupsPanel = model.groupsPanel;
+      this.state.model.permission = model.permission;
     },
     updatePermissionsPanel(state, permissionsPanel) {
       this.state.model.permissionsPanel = permissionsPanel;
     },
     updateMembersPanel(state, membersPanel) {
       this.state.model.membersPanel = membersPanel;
+    },
+    setTabIndex(state, tabIndex) {
+      // this will trigger the tabChanged() method in Main.vue
+      this.state.input.tabIndex = tabIndex;
+    },
+    setLoading(state, loading) {
+      this.state.loading = loading;
     }
   },
   getters: {
diff --git a/gms-ui/vue.config.js b/gms-ui/vue.config.js
index 06fc6e5..faddadb 100644
--- a/gms-ui/vue.config.js
+++ b/gms-ui/vue.config.js
@@ -1,4 +1,4 @@
-const path = require('path')
+const path = require('path');
 
 module.exports = {
   chainWebpack: config => {
-- 
GitLab