Refactor LoadingIndicator to take slot and implement it into SearchResult view
authorAdam Dullage <redacted>
Sun, 5 May 2024 10:11:15 +0000 (11:11 +0100)
committerAdam Dullage <redacted>
Sun, 5 May 2024 10:11:15 +0000 (11:11 +0100)
client/components/LoadingIndicator.vue
client/views/Note.vue
client/views/SearchResults.vue

index 6ec85d40e00b8fbe48c91ca5201c07acb8d16833..0b8a88bf657b8d708c606513ff3b125fe63673f6 100644 (file)
@@ -1,7 +1,13 @@
 <template>
-  <div>
-    <div v-if="!hideLoader && !failed" class="loader"></div>
-    <div v-else-if="failed" class="flex flex-col items-center">
+  <div :class="{ 'flex items-center justify-center': loadSuccessful !== true }">
+    <!-- Loading -->
+    <div
+      v-if="loadSuccessful === null && !props.hideLoader"
+      class="loader"
+    ></div>
+
+    <!-- Failed -->
+    <div v-else-if="loadSuccessful === false" class="flex flex-col items-center">
       <SvgIcon
         type="mdi"
         :path="failedIconPath"
@@ -10,6 +16,9 @@
       />
       <span class="text-lg text-theme-text-muted">{{ failedMessage }}</span>
     </div>
+
+    <!-- Loaded -->
+    <slot v-else-if="loadSuccessful"></slot>
   </div>
 </template>
 
@@ -18,19 +27,27 @@ import SvgIcon from "@jamescoyle/vue-icon";
 import { mdiTrafficCone } from "@mdi/js";
 import { ref } from "vue";
 
-defineProps({ hideLoader: Boolean });
+const props = defineProps({ hideLoader: Boolean });
 
-const failed = ref(false);
+const loadSuccessful = ref(null);
 const failedIconPath = ref("");
 const failedMessage = ref("");
 
+function setLoading() {
+  loadSuccessful.value = null;
+}
+
 function setFailed(message, iconPath) {
-  failed.value = true;
   failedMessage.value = message || "Loading Failed";
   failedIconPath.value = iconPath || mdiTrafficCone;
+  loadSuccessful.value = false;
+}
+
+function setLoaded() {
+  loadSuccessful.value = true;
 }
 
-defineExpose({ setFailed });
+defineExpose({ setLoading, setFailed, setLoaded });
 </script>
 
 <style scoped>
index 541a67ec63c1b4d9eb68c61f5df28942310dbe65..08c2d4683ee46daf39e777eae39e80248683b9b7 100644 (file)
@@ -1,78 +1,68 @@
 <template>
-  <div class="h-full">
-    <!-- Loading -->
-    <LoadingIndicator
-      v-if="showLoadingIndicator"
-      ref="loadingIndicator"
-      class="flex h-full items-center justify-center"
-    />
-
-    <!-- Loaded -->
-    <div v-else class="flex h-full flex-col">
-      <!-- Confirm Deletion Modal -->
-      <ConfirmModal
-        ref="deleteConfirmModal"
-        title="Confirm Deletion"
-        :message="`Are you sure you want to delete the note '${note.title}'?`"
-        isDanger
-        @confirm="deleteConfirmedHandler"
-      />
-
-      <!-- Header -->
-      <div class="flex flex-wrap-reverse items-start">
-        <!-- Title -->
-        <div class="flex flex-1 text-3xl leading-[1.6em]">
-          <span v-show="!editMode" class="flex-1 text-nowrap">{{
-            note.title
-          }}</span>
-          <input
-            v-show="editMode"
-            v-model="newTitle"
-            class="flex-1 bg-theme-background outline-none"
-            placeholder="Title"
-          />
-        </div>
-
-        <!-- Buttons -->
-        <div v-show="!editMode">
-          <CustomButton
-            :iconPath="mdilDelete"
-            label="Delete"
-            @click="deleteHandler"
-          />
-          <CustomButton
-            :iconPath="mdilPencil"
-            label="Edit"
-            @click="editHandler"
-          />
-        </div>
-        <div v-show="editMode">
-          <CustomButton
-            :iconPath="mdilArrowLeft"
-            label="Cancel"
-            @click="cancelHandler"
-          />
-          <CustomButton
-            :iconPath="mdilContentSave"
-            label="Save"
-            @click="saveHandler"
-          />
-        </div>
+  <!-- Confirm Deletion Modal -->
+  <ConfirmModal
+    ref="deleteConfirmModal"
+    title="Confirm Deletion"
+    :message="`Are you sure you want to delete the note '${note.title}'?`"
+    isDanger
+    @confirm="deleteConfirmedHandler"
+  />
+
+  <LoadingIndicator ref="loadingIndicator" class="flex h-full flex-col">
+    <!-- Header -->
+    <div class="flex flex-wrap-reverse items-start">
+      <!-- Title -->
+      <div class="flex flex-1 text-3xl leading-[1.6em]">
+        <span v-show="!editMode" class="flex-1 text-nowrap">{{
+          note.title
+        }}</span>
+        <input
+          v-show="editMode"
+          v-model="newTitle"
+          class="flex-1 bg-theme-background outline-none"
+          placeholder="Title"
+        />
       </div>
 
-      <hr v-if="!editMode" class="my-4 border-theme-border" />
-
-      <!-- Content -->
-      <div class="flex-1">
-        <ToastViewer v-if="!editMode" :initialValue="note.content" />
-        <ToastEditor
-          v-if="editMode"
-          ref="toastEditor"
-          :initialValue="note.content"
+      <!-- Buttons -->
+      <div v-show="!editMode">
+        <CustomButton
+          :iconPath="mdilDelete"
+          label="Delete"
+          @click="deleteHandler"
+        />
+        <CustomButton
+          :iconPath="mdilPencil"
+          label="Edit"
+          @click="editHandler"
+        />
+      </div>
+      <div v-show="editMode">
+        <CustomButton
+          :iconPath="mdilArrowLeft"
+          label="Cancel"
+          @click="cancelHandler"
+        />
+        <CustomButton
+          :iconPath="mdilContentSave"
+          label="Save"
+          @click="saveHandler"
         />
       </div>
     </div>
-  </div>
+
+    <hr v-if="!editMode" class="my-4 border-theme-border" />
+
+    <!-- Content -->
+    <div class="flex-1">
+      <ToastViewer v-if="!editMode" :initialValue="note.content" />
+      <ToastEditor
+        v-if="editMode"
+        ref="toastEditor"
+        :initialValue="note.content"
+      />
+    </div>
+  </LoadingIndicator>
 </template>
 
 <script setup>
@@ -95,8 +85,8 @@ import LoadingIndicator from "../components/LoadingIndicator.vue";
 import ToastEditor from "../components/toastui/ToastEditor.vue";
 import ToastViewer from "../components/toastui/ToastViewer.vue";
 import {
-  getUnknownServerErrorToastOptions,
   getToastOptions,
+  getUnknownServerErrorToastOptions,
 } from "../helpers.js";
 
 const props = defineProps({
@@ -109,7 +99,6 @@ const isNewNote = computed(() => !props.title);
 const loadingIndicator = ref();
 const note = ref({});
 const router = useRouter();
-const showLoadingIndicator = ref(true);
 const newTitle = ref();
 const toast = useToast();
 const toastEditor = ref();
@@ -120,12 +109,12 @@ function init() {
     return;
   }
 
-  showLoadingIndicator.value = true;
+  loadingIndicator.value?.setLoading(); // #CS
   if (props.title) {
     getNote(props.title)
       .then((data) => {
         note.value = data;
-        showLoadingIndicator.value = false;
+        loadingIndicator.value.setLoaded();
       })
       .catch((error) => {
         if (error.response.status === 404) {
@@ -139,7 +128,7 @@ function init() {
     newTitle.value = "";
     note.value = new Note();
     editMode.value = true;
-    showLoadingIndicator.value = false;
+    loadingIndicator.value.setLoaded();
   }
 }
 
index e31b42c801e9b843f269022c86083f1241d96bb9..900bb160cf5e2933a940c80e56ced4cbb1cd5bd4 100644 (file)
@@ -1,32 +1,37 @@
 <template>
-  <div class="max-w-[700px]">
+  <div class="flex h-full flex-col">
     <!-- Search Input -->
-    <SearchInput :initialSearchTerm="props.searchTerm" class="mb-8" />
+    <SearchInput
+      :initialSearchTerm="props.searchTerm"
+      class="mb-8 max-w-[700px]"
+    />
 
     <!-- Search Results -->
-    <div
-      v-for="result in results"
-      class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-tint dark:hover:bg-theme-background-elevated"
-    >
-      <RouterLink :to="result.href">
-        <!-- Title and Tags -->
-        <div>
-          <span v-html="result.titleHighlightsOrTitle" class="mr-2"></span>
-          <Tag v-for="tag in result.tagMatches" :tag="tag" class="mr-1" />
-        </div>
-        <!-- Last Modified and Content Highlights -->
-        <div>
-          <span class="text-theme-text-muted">{{
-            result.lastModifiedAsString
-          }}</span>
-          <span v-if="result.contentHighlights"> - </span>
-          <span
-            v-html="result.contentHighlights"
-            class="text-theme-text-muted"
-          ></span>
-        </div>
-      </RouterLink>
-    </div>
+    <LoadingIndicator ref="loadingIndicator" class="max-w-[700px] flex-1">
+      <div
+        v-for="result in results"
+        class="mb-4 cursor-pointer rounded px-2 py-1 hover:bg-theme-background-tint dark:hover:bg-theme-background-elevated"
+      >
+        <RouterLink :to="result.href">
+          <!-- Title and Tags -->
+          <div>
+            <span v-html="result.titleHighlightsOrTitle" class="mr-2"></span>
+            <Tag v-for="tag in result.tagMatches" :tag="tag" class="mr-1" />
+          </div>
+          <!-- Last Modified and Content Highlights -->
+          <div>
+            <span class="text-theme-text-muted">{{
+              result.lastModifiedAsString
+            }}</span>
+            <span v-if="result.contentHighlights"> - </span>
+            <span
+              v-html="result.contentHighlights"
+              class="text-theme-text-muted"
+            ></span>
+          </div>
+        </RouterLink>
+      </div>
+    </LoadingIndicator>
   </div>
 </template>
 
 import { useToast } from "primevue/usetoast";
 import { ref, watch } from "vue";
 
+import { mdiMagnify } from "@mdi/js";
 import { getNotes } from "../api.js";
+import LoadingIndicator from "../components/LoadingIndicator.vue";
+import Tag from "../components/Tag.vue";
 import { getUnknownServerErrorToastOptions } from "../helpers.js";
 import SearchInput from "../partials/SearchInput.vue";
-import Tag from "../components/Tag.vue";
 
 const props = defineProps({ searchTerm: String });
 
+const loadingIndicator = ref();
 const results = ref([]);
 const toast = useToast();
 
 function init() {
+  loadingIndicator.value?.setLoading(); // #CS
   getNotes(props.searchTerm)
     .then((data) => {
       results.value = data;
+      if (results.value.length > 0) {
+        loadingIndicator.value.setLoaded();
+      } else {
+        loadingIndicator.value.setFailed("No Results", mdiMagnify);
+      }
     })
     .catch(() => {
+      loadingIndicator.value.setFailed();
       toast.add(getUnknownServerErrorToastOptions());
     });
 }
git clone https://git.99rst.org/PROJECT