From bd39d403a5f41e4799c5e682cb5cb3923d4ba1c2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de>
Date: Mon, 8 Aug 2022 17:42:16 +0200
Subject: [PATCH] Refactor the Bucket view to decouple it from the route
 parameters

#17
---
 src/components/BucketView.vue | 145 +++++++++++++++++-----------------
 src/router/index.ts           |   1 +
 2 files changed, 72 insertions(+), 74 deletions(-)

diff --git a/src/components/BucketView.vue b/src/components/BucketView.vue
index d45d20f..ba9ad1c 100644
--- a/src/components/BucketView.vue
+++ b/src/components/BucketView.vue
@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import { useRoute } from "vue-router";
 import { onMounted, reactive, watch, computed } from "vue";
 import type { ComputedRef } from "vue";
 import type { S3ObjectMetaInformation } from "@/client";
@@ -10,7 +9,23 @@ import dayjs from "dayjs";
 
 // Constants
 // -----------------------------------------------------------------------------
-const route = useRoute();
+
+const props = defineProps<{
+  bucketName: string;
+  subFolders: string[] | string;
+}>();
+
+const currentSubFolders: ComputedRef<string[]> = computed(() => {
+  /**
+   * Transform a single sub folder from a string to an array containing the string and
+   * replace an empty string with an empty list
+   */
+  return props.subFolders instanceof Array
+    ? props.subFolders
+    : props.subFolders.length > 0
+    ? [props.subFolders]
+    : [];
+});
 
 // Typescript types
 // -----------------------------------------------------------------------------
@@ -36,13 +51,11 @@ type FolderTree = {
 // -----------------------------------------------------------------------------
 const objectState = reactive({
   objects: [],
-  visibleObjects: [],
   loading: true,
   bucketNotFoundError: false,
   bucketPermissionError: false,
 } as {
   objects: S3ObjectMetaInformation[];
-  visibleObjects: (S3ObjectWithFolder | S3PseudoFolder)[];
   loading: boolean;
   bucketNotFoundError: boolean;
   bucketPermissionError: boolean;
@@ -51,33 +64,15 @@ const objectState = reactive({
 // Watcher
 // -----------------------------------------------------------------------------
 watch(
-  () => route.params,
-  (newRouteParams, oldRouteParams) => {
-    if (
-      newRouteParams.bucketName &&
-      oldRouteParams.bucketName !== newRouteParams.bucketName
-    ) {
+  () => props.bucketName,
+  (newBucketName, oldBucketName) => {
+    if (oldBucketName !== newBucketName) {
       // If bucket is changed, update the objects
-      updateObjects(newRouteParams.bucketName as string);
-    } else if (
-      newRouteParams.subFolders &&
-      oldRouteParams.subFolders !== newRouteParams.subFolders
-    ) {
-      // If sub folder is changed, update the visible objects
-      updateVisibleObjects(newRouteParams.subFolders as string[]);
+      updateObjects(newBucketName);
     }
   }
 );
 
-watch(
-  () => objectState.objects,
-  () => {
-    updateVisibleObjects(
-      subFolderInUrl.value ? (route.params.subFolders as string[]) : []
-    );
-  }
-);
-
 // Computed Properties
 // -----------------------------------------------------------------------------
 const folderStructure: ComputedRef<FolderTree> = computed(() => {
@@ -141,7 +136,7 @@ const objectsWithFolders: ComputedRef<S3ObjectWithFolder[]> = computed(() => {
 });
 
 const subFolderInUrl: ComputedRef<boolean> = computed(
-  () => route.params.subFolders != null && route.params.subFolders.length > 0
+  () => currentSubFolders.value.length > 0
 );
 const errorLoadingObjects: ComputedRef<boolean> = computed(
   () => objectState.bucketPermissionError || objectState.bucketNotFoundError
@@ -150,7 +145,7 @@ const errorLoadingObjects: ComputedRef<boolean> = computed(
 // Lifecycle Hooks
 // -----------------------------------------------------------------------------
 onMounted(() => {
-  updateObjects(route.params.bucketName as string);
+  updateObjects(props.bucketName);
 });
 
 // Functions
@@ -217,41 +212,43 @@ function updateObjects(bucketName: string) {
     });
 }
 
-/**
- * Update the visible objects based on the current sub folder
- * @param subFolders sub folders as ordered array
- */
-function updateVisibleObjects(subFolders: string[]) {
-  objectState.visibleObjects = [];
-  let currentFolder = folderStructure.value;
-  for (const subFolder of subFolders) {
-    if (currentFolder.subFolders[subFolder] == null) {
-      return;
-    } else {
-      currentFolder = currentFolder.subFolders[subFolder];
+const visibleObjects: ComputedRef<(S3ObjectWithFolder | S3PseudoFolder)[]> =
+  computed(() => {
+    /**
+     * Compute the visible objects based on the current sub folder
+     */
+    let currentFolder = folderStructure.value;
+    // Navigate into right sub folder
+    for (const subFolder of currentSubFolders.value) {
+      if (currentFolder.subFolders[subFolder] == null) {
+        // If sub folder doesn't exist, no object is visible
+        return [];
+      } else {
+        currentFolder = currentFolder.subFolders[subFolder];
+      }
     }
-  }
-  const arr = [];
-  arr.push(...currentFolder.files);
-  arr.push(
-    ...Object.keys(currentFolder.subFolders).map((subFolderName) => {
-      const folderSize = calculateFolderSize(
-        currentFolder.subFolders[subFolderName]
-      );
-      const folderLastModified = dayjs(
-        calculateFolderLastModified(currentFolder.subFolders[subFolderName])
-      ).toISOString();
-      return {
-        name: subFolderName,
-        size: folderSize,
-        key: subFolderName,
-        parentFolder: subFolders,
-        last_modified: folderLastModified,
-      } as S3PseudoFolder;
-    })
-  );
-  objectState.visibleObjects = arr;
-}
+    // Add all objects and sub folders from the current sub folder as visible object
+    const arr = [];
+    arr.push(...currentFolder.files);
+    arr.push(
+      ...Object.keys(currentFolder.subFolders).map((subFolderName) => {
+        const folderSize = calculateFolderSize(
+          currentFolder.subFolders[subFolderName]
+        );
+        const folderLastModified = dayjs(
+          calculateFolderLastModified(currentFolder.subFolders[subFolderName])
+        ).toISOString();
+        return {
+          name: subFolderName,
+          size: folderSize,
+          key: subFolderName,
+          parentFolder: currentSubFolders.value,
+          last_modified: folderLastModified,
+        } as S3PseudoFolder;
+      })
+    );
+    return arr;
+  });
 
 function isS3Object(
   obj: S3PseudoFolder | S3ObjectWithFolder
@@ -269,25 +266,25 @@ function isS3Object(
           v-if="subFolderInUrl"
           :to="{
             name: 'bucket',
-            params: { bucketName: route.params.bucketName, subFolders: [] },
+            params: { bucketName: props.bucketName, subFolders: [] },
           }"
-          >{{ route.params.bucketName }}
+          >{{ props.bucketName }}
         </router-link>
-        <span v-else>{{ route.params.bucketName }}</span>
+        <span v-else>{{ props.bucketName }}</span>
       </li>
       <li
         class="breadcrumb-item"
-        v-for="(folder, index) in route.params.subFolders"
+        v-for="(folder, index) in currentSubFolders"
         :key="folder"
-        :class="{ active: index === route.params.subFolders.length }"
+        :class="{ active: index === currentSubFolders.length }"
       >
         <router-link
-          v-if="index !== route.params.subFolders.length - 1"
+          v-if="index !== currentSubFolders.length - 1"
           :to="{
             name: 'bucket',
             params: {
-              bucketName: route.params.bucketName,
-              subFolders: route.params.subFolders.slice(0, index + 1),
+              bucketName: props.bucketName,
+              subFolders: currentSubFolders.slice(0, index + 1),
             },
           }"
           >{{ folder }}
@@ -363,7 +360,7 @@ function isS3Object(
         <caption>
           Displaying
           {{
-            objectState.loading ? 0 : objectState.visibleObjects.length
+            objectState.loading ? 0 : visibleObjects.length
           }}
           Objects
         </caption>
@@ -387,7 +384,7 @@ function isS3Object(
           </tr>
         </tbody>
         <!-- Table body when no objects are in the bcuket -->
-        <tbody v-else-if="objectState.visibleObjects.length === 0">
+        <tbody v-else-if="visibleObjects.length === 0">
           <tr>
             <td colspan="4" class="text-center fst-italic fw-light">
               No objects to display
@@ -396,7 +393,7 @@ function isS3Object(
         </tbody>
         <!-- Table body when showing objects -->
         <tbody v-else>
-          <tr v-for="obj in objectState.visibleObjects" :key="obj.key">
+          <tr v-for="obj in visibleObjects" :key="obj.key">
             <th scope="row" class="text-truncate">
               <!-- Show file name if row is an object -->
               <div v-if="isS3Object(obj)">{{ obj.pseudoFileName }}</div>
@@ -407,7 +404,7 @@ function isS3Object(
                   :to="{
                     name: 'bucket',
                     params: {
-                      bucketName: route.params.bucketName,
+                      bucketName: props.bucketName,
                       subFolders: obj.parentFolder.concat(obj.name),
                     },
                   }"
diff --git a/src/router/index.ts b/src/router/index.ts
index 1c4428c..15fec02 100644
--- a/src/router/index.ts
+++ b/src/router/index.ts
@@ -19,6 +19,7 @@ const router = createRouter({
               path: ":bucketName/:subFolders*",
               name: "bucket",
               component: () => import("../components/BucketView.vue"),
+              props: true,
             },
           ],
         },
-- 
GitLab