<script setup lang="ts"> import { computed, onMounted, reactive } from "vue"; import { useResourceStore } from "@/stores/resources"; import ResourceCard from "@/components/resources/ResourceCard.vue"; import CardTransitionGroup from "@/components/transitions/CardTransitionGroup.vue"; import { useAuthStore } from "@/stores/users"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import type { ResourceOut } from "@/client/resource"; const resourceRepository = useResourceStore(); const userRepository = useAuthStore(); const resourceState = reactive<{ loading: boolean; filterString: string; sortDesc: boolean; }>({ loading: true, filterString: "", sortDesc: true, }); const sortedResourcesByName = computed<ResourceOut[]>(() => { return [...resourceRepository.resources].sort((a, b) => a.name < b.name ? 1 : -1, ); }); const sortedResources = computed<ResourceOut[]>(() => { if (resourceState.sortDesc) { return [...sortedResourcesByName.value].reverse(); } return sortedResourcesByName.value; }); const filteredSortedResources = computed<ResourceOut[]>(() => { return sortedResources.value.filter((resource) => { return resourceState.filterString.length > 0 ? resource.name.includes(resourceState.filterString) : true; }); }); onMounted(() => { resourceRepository .fetchPublicResources(() => { resourceState.loading = false; }) .then((resources) => { userRepository.fetchUsernames(resources.map((r) => r.maintainer_id)); }); }); </script> <template> <div class="row border-bottom mb-4"> <h2 class="mb-2">Available Resources</h2> </div> <div class="d-flex m-2 mb-3 align-items-center justify-content-start"> <div class="col-5"> <div class="input-group rounded shadow-sm"> <span class="input-group-text" id="resources-search-wrapping" ><font-awesome-icon icon="fa-solid fa-magnifying-glass" /></span> <input type="text" id="filterResourcesInput" class="form-control" placeholder="Filter Resources" aria-label="Filter Resources" aria-describedby="resources-search-wrapping" :disabled="resourceState.loading" v-model.trim="resourceState.filterString" maxlength="20" /> </div> </div> <font-awesome-icon :icon=" resourceState.sortDesc ? 'fa-solid fa-arrow-down-wide-short' : 'fa-solid fa-arrow-up-wide-short' " @click="resourceState.sortDesc = !resourceState.sortDesc" class="fs-5 ms-3 cursor-pointer" /> </div> <div v-if="!resourceState.loading"> <div v-if="resourceRepository.resources.length === 0" class="text-center fs-2 mt-5" > <font-awesome-icon icon="fa-solid fa-x" class="my-5 fs-0" style="color: var(--bs-secondary)" /> <p>There are no resources in the system. Please come again later.</p> </div> <div v-else-if="filteredSortedResources.length === 0" class="text-center fs-2 mt-5" > <font-awesome-icon icon="fa-solid fa-magnifying-glass" class="my-5 fs-0" style="color: var(--bs-secondary)" /> <p> Could not find any Resources containing<br />'{{ resourceState.filterString }}' </p> </div> <CardTransitionGroup v-else class="d-flex flex-wrap align-items-center justify-content-between" > <resource-card v-for="resource in filteredSortedResources" :key="resource.resource_id" :resource="resource" :loading="false" style="min-width: 47%; max-width: 48%" /> </CardTransitionGroup> </div> <div v-else class="d-flex flex-wrap align-items-center justify-content-between" > <resource-card v-for="resource in 4" :key="resource" :resource="{ name: '', description: '', source: '', resource_id: '', versions: [], maintainer_id: '', }" style="min-width: 48%" loading /> </div> </template> <style scoped></style>