airavata-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From machris...@apache.org
Subject [airavata-django-portal] 02/02: AIRAVATA-3115 UI for toggling favorites
Date Thu, 18 Jul 2019 20:15:31 GMT
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit f8ff4763453b12069ae62d50785e146292a3fa6e
Author: Marcus Christie <machristie@apache.org>
AuthorDate: Thu Jul 18 16:14:21 2019 -0400

    AIRAVATA-3115 UI for toggling favorites
---
 .../js/models/WorkspacePreferences.js              |   2 +-
 .../django_airavata_api/js/service_config.js       |   8 ++
 .../js/containers/DashboardContainer.vue           | 102 ++++++++++++++++++++-
 .../common/js/components/ApplicationCard.vue       |  76 +++++++++------
 .../static/common/js/components/FavoriteToggle.vue |  61 ++++++++++++
 django_airavata/static/common/js/index.js          |   2 +
 6 files changed, 217 insertions(+), 34 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
b/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
index f0cf1ff..34eb4b3 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/WorkspacePreferences.js
@@ -1,6 +1,6 @@
 import BaseModel from "./BaseModel";
 
-const FIELDS = ["most_recent_project_id"];
+const FIELDS = ["most_recent_project_id", "application_preferences"];
 
 export default class WorkspacePreferences extends BaseModel {
   constructor(data = {}) {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 82261a6..002cd74 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -96,6 +96,14 @@ export default {
         url: "/api/applications/list_all/",
         requestType: "get",
         modelClass: ApplicationModule
+      },
+      favorite: {
+        url: "/api/applications/<lookup>/favorite/",
+        requestType: "post"
+      },
+      unfavorite: {
+        url: "/api/applications/<lookup>/unfavorite/",
+        requestType: "post"
       }
     },
     modelClass: ApplicationModule
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
index b833d23..ef5c95f 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/containers/DashboardContainer.vue
@@ -20,17 +20,53 @@
           contact to grant you the appropriate privileges.</b-alert>
       </div>
     </div>
+    <template v-if="favoriteApplicationsData.length > 0">
+      <div class="row">
+        <div class="col">
+          <h1 class="h5 mb-2">Favorites</h1>
+        </div>
+      </div>
+      <div class="row">
+        <application-card
+          v-for="item in favoriteApplicationsData"
+          v-bind:appModule="item.appModule"
+          v-bind:key="item.appModule.appModuleId"
+          @app-selected="handleAppSelected"
+          :disabled="item.disabled"
+          @favorite="markFavorite(item.appModule)"
+          @unfavorite="markNotFavorite(item.appModule)"
+          ref="favoriteApplicationCards"
+        >
+          <favorite-toggle
+            slot="card-actions"
+            :favorite="true"
+            class="card-link"
+            @favorite="markFavorite(item.appModule)"
+            @unfavorite="markNotFavorite(item.appModule)"
+          />
+        </application-card>
+      </div>
+      <hr />
+    </template>
     <div class="row">
       <application-card
-        v-for="item in allApplicationData"
+        v-for="item in nonFavoriteApplicationsData"
         v-bind:appModule="item.appModule"
         v-bind:key="item.appModule.appModuleId"
         @app-selected="handleAppSelected"
         :disabled="item.disabled"
+        @favorite="markFavorite(item.appModule)"
+        @unfavorite="markNotFavorite(item.appModule)"
       >
+        <favorite-toggle
+          slot="card-actions"
+          :favorite="false"
+          class="card-link"
+          @favorite="markFavorite(item.appModule)"
+          @unfavorite="markNotFavorite(item.appModule)"
+        />
       </application-card>
     </div>
-    </div>
 
   </div>
 </template>
@@ -46,15 +82,46 @@ export default {
     return {
       accessibleAppModules: null,
       userProfile: null,
-      allApplicationModules: null
+      allApplicationModules: null,
+      workspacePreferences: null
     };
   },
   components: {
-    "application-card": comps.ApplicationCard
+    "application-card": comps.ApplicationCard,
+    "favorite-toggle": comps.FavoriteToggle
   },
   methods: {
     handleAppSelected: function(appModule) {
       urls.navigateToCreateExperiment(appModule);
+    },
+    markFavorite(appModule) {
+      services.ApplicationModuleService.favorite({
+        lookup: appModule.appModuleId
+      })
+        .then(() => {
+          return services.WorkspacePreferencesService.get().then(
+            prefs => (this.workspacePreferences = prefs)
+          );
+        })
+        .then(() => {
+          const index = this.favoriteApplicationsData.findIndex(
+            data => data.appModule.appModuleId === appModule.appModuleId
+          );
+          this.$nextTick(() => {
+            this.$refs.favoriteApplicationCards[index].$el.scrollIntoView({
+              behavior: "smooth", block: "center"
+            });
+          });
+        });
+    },
+    markNotFavorite(appModule) {
+      services.ApplicationModuleService.unfavorite({
+        lookup: appModule.appModuleId
+      }).then(() => {
+        return services.WorkspacePreferencesService.get().then(
+          prefs => (this.workspacePreferences = prefs)
+        );
+      });
     }
   },
   computed: {
@@ -86,6 +153,30 @@ export default {
             };
           })
         : [];
+    },
+    favoriteApplicationsData() {
+      return this.allApplicationData.filter(
+        app =>
+          this.favoriteApplicationIds.indexOf(app.appModule.appModuleId) >= 0
+      );
+    },
+    nonFavoriteApplicationsData() {
+      return this.allApplicationData.filter(
+        app =>
+          this.favoriteApplicationIds.indexOf(app.appModule.appModuleId) < 0
+      );
+    },
+    favoriteApplicationIds() {
+      if (
+        this.workspacePreferences &&
+        this.workspacePreferences.application_preferences
+      ) {
+        return this.workspacePreferences.application_preferences
+          .filter(p => p.favorite)
+          .map(p => p.application_id);
+      } else {
+        return [];
+      }
     }
   },
   beforeMount: function() {
@@ -99,6 +190,9 @@ export default {
     services.ApplicationModuleService.listAll().then(
       result => (this.allApplicationModules = result)
     );
+    services.WorkspacePreferencesService.get().then(
+      prefs => (this.workspacePreferences = prefs)
+    );
   }
 };
 </script>
diff --git a/django_airavata/static/common/js/components/ApplicationCard.vue b/django_airavata/static/common/js/components/ApplicationCard.vue
index 5e2e4d8..756f7e2 100644
--- a/django_airavata/static/common/js/components/ApplicationCard.vue
+++ b/django_airavata/static/common/js/components/ApplicationCard.vue
@@ -1,44 +1,62 @@
 <template>
-    <div class="col-md-6 col-xl-4">
-        <div class="card application-card" :class="cardClasses">
-            <b-link :disabled="disabled" class="card-link text-dark" @click.prevent="handleAppClick">
-                <div class="card-body"  >
-                    <h2 class="card-title h5">{{appModule.appModuleName}}</h2>
-                    <span class="badge badge-primary mr-1" v-for="tag in appModule.tags"
:key="tag">{{tag}}</span>
-                    <span class="badge badge-primary mr-1" v-if="appModule.appModuleVersion"
>{{appModule.appModuleVersion}}</span>
-                    <p class="card-text card-text--small mt-3 text-secondary">{{appModule.appModuleDescription}}</p>
-                </div>
-            </b-link>
+  <div class="col-md-6 col-xl-4">
+    <div
+      class="card application-card"
+      :class="cardClasses"
+    >
+      <b-link
+        :disabled="disabled"
+        class="card-link text-dark"
+        @click.prevent="handleAppClick"
+      >
+        <div class="card-body">
+          <h2 class="card-title h5">{{appModule.appModuleName}}</h2>
+          <span
+            class="badge badge-primary mr-1"
+            v-for="tag in appModule.tags"
+            :key="tag"
+          >{{tag}}</span>
+          <span
+            class="badge badge-primary mr-1"
+            v-if="appModule.appModuleVersion"
+          >{{appModule.appModuleVersion}}</span>
+          <p class="card-text card-text--small mt-3 text-secondary">{{appModule.appModuleDescription}}</p>
+          <p class="card-text">
+            <slot name="card-actions">
+            </slot>
+          </p>
         </div>
+      </b-link>
     </div>
+  </div>
 </template>
 
 <script>
 export default {
-    name: 'application-card',
-    props: ['appModule', 'disabled'],
-    data:function () {
-        return {
-        };
-    },
-    methods: {
-        handleAppClick: function() {
-            this.$emit('app-selected', this.appModule);
-        }
-    },
-    computed: {
-      cardClasses() {
-        return this.disabled ? ['is-disabled'] : [];
-      },
+  name: "application-card",
+  props: ["appModule", "disabled"],
+  data: function() {
+    return {};
+  },
+  methods: {
+    handleAppClick: function() {
+      this.$emit("app-selected", this.appModule);
     }
-}
+  },
+  computed: {
+    cardClasses() {
+      return this.disabled ? ["is-disabled"] : [];
+    }
+  }
+};
 </script>
 
 <style>
 .application-card {
-    height: calc(100% - 30px); /* 30px margin at the botton */
+  height: calc(100% - 30px); /* 30px margin at the botton */
 }
-.application-card .card-link, .application-card .card-body {
-    height: 100%;
+.application-card .card-link,
+.application-card .card-body {
+  height: 100%;
 }
 </style>
diff --git a/django_airavata/static/common/js/components/FavoriteToggle.vue b/django_airavata/static/common/js/components/FavoriteToggle.vue
new file mode 100644
index 0000000..671554e
--- /dev/null
+++ b/django_airavata/static/common/js/components/FavoriteToggle.vue
@@ -0,0 +1,61 @@
+<template>
+  <b-link
+    class="text-primary"
+    @click.stop="toggleFavorite"
+    v-b-tooltip
+    :title="titleText"
+  >
+    <i
+      class="fa fa-star favorite-toggle"
+      :class="classes"
+    ><span class="sr-only">Toggle favorite</span></i>
+  </b-link>
+</template>
+
+<script>
+export default {
+  name: "favorite-toggle",
+  props: {
+    favorite: {
+      type: Boolean,
+      default: false
+    }
+  },
+  methods: {
+    toggleFavorite() {
+      if (this.favorite) {
+        this.$emit("unfavorite");
+      } else {
+        this.$emit("favorite");
+      }
+    }
+  },
+  computed: {
+    classes() {
+      if (!this.favorite) {
+        return ["not-favorite"];
+      } else {
+        return [];
+      }
+    },
+    titleText() {
+      if (this.favorite) {
+        return "Unmark as favorite";
+      } else {
+        return "Mark as favorite";
+      }
+    }
+  }
+};
+</script>
+
+<style scoped>
+.not-favorite {
+  color: #999999;
+}
+.not-favorite:hover {
+  color: inherit;
+}
+</style>
+
+
diff --git a/django_airavata/static/common/js/index.js b/django_airavata/static/common/js/index.js
index b48c033..9bec96e 100644
--- a/django_airavata/static/common/js/index.js
+++ b/django_airavata/static/common/js/index.js
@@ -9,6 +9,7 @@ import DataProductViewer from "./components/DataProductViewer";
 import DeleteButton from "./components/DeleteButton.vue";
 import DeleteLink from "./components/DeleteLink.vue";
 import ExperimentStatusBadge from "./components/ExperimentStatusBadge";
+import FavoriteToggle from "./components/FavoriteToggle";
 import GatewayGroupsBadge from "./components/GatewayGroupsBadge";
 import HumanDate from "./components/HumanDate.vue";
 import MainLayout from "./components/MainLayout.vue";
@@ -46,6 +47,7 @@ const components = {
   DeleteButton,
   DeleteLink,
   ExperimentStatusBadge,
+  FavoriteToggle,
   GatewayGroupsBadge,
   HumanDate,
   MainLayout,


Mime
View raw message