You've already forked AstralRinth
forked from didirus/AstralRinth
* Search updates * fixes2 * Some more work * start instance page wiring * Pack installation + Profile viewing * Remove print statement * Fix disappearing profiles * fix compile err * Finish Instance Running * remove print statement * fix prettier * Fix clippy + early return
361 lines
9.4 KiB
Vue
361 lines
9.4 KiB
Vue
<script setup>
|
|
import { ref } from 'vue'
|
|
import { ofetch } from 'ofetch'
|
|
import {
|
|
Pagination,
|
|
ProjectCard,
|
|
Checkbox,
|
|
Button,
|
|
ClearIcon,
|
|
SearchIcon,
|
|
DropdownSelect,
|
|
SearchFilter,
|
|
Card,
|
|
ClientIcon,
|
|
ServerIcon,
|
|
AnimatedLogo,
|
|
} from 'omorphia'
|
|
import Multiselect from 'vue-multiselect'
|
|
import { useSearch } from '@/store/state'
|
|
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
|
|
|
const searchStore = useSearch()
|
|
|
|
const showSnapshots = ref(false)
|
|
const loading = ref(true)
|
|
|
|
const [categories, loaders, availableGameVersions] = await Promise.all([
|
|
get_categories(),
|
|
get_loaders(),
|
|
get_game_versions(),
|
|
])
|
|
|
|
const getSearchResults = async (shouldLoad = false) => {
|
|
const queryString = searchStore.getQueryString()
|
|
if (shouldLoad === true) {
|
|
loading.value = true
|
|
}
|
|
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
|
|
loading.value = false
|
|
searchStore.setSearchResults(response)
|
|
}
|
|
|
|
getSearchResults(true)
|
|
|
|
const toggleFacet = async (facet) => {
|
|
const index = searchStore.facets.indexOf(facet)
|
|
|
|
if (index !== -1) searchStore.facets.splice(index, 1)
|
|
else searchStore.facets.push(facet)
|
|
|
|
await getSearchResults()
|
|
}
|
|
|
|
const toggleOrFacet = async (orFacet) => {
|
|
const index = searchStore.orFacets.indexOf(orFacet)
|
|
|
|
if (index !== -1) searchStore.orFacets.splice(index, 1)
|
|
else searchStore.orFacets.push(orFacet)
|
|
|
|
await getSearchResults()
|
|
}
|
|
|
|
const switchPage = async (page) => {
|
|
searchStore.currentPage = parseInt(page)
|
|
if (page === 1) searchStore.offset = 0
|
|
else searchStore.offset = (searchStore.currentPage - 1) * searchStore.limit
|
|
await getSearchResults()
|
|
}
|
|
|
|
const handleReset = async () => {
|
|
searchStore.resetFilters()
|
|
await getSearchResults()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="search-container">
|
|
<aside class="filter-panel">
|
|
<Button
|
|
role="button"
|
|
:disabled="
|
|
!(
|
|
searchStore.facets.length > 0 ||
|
|
searchStore.orFacets.length > 0 ||
|
|
searchStore.environments.server === true ||
|
|
searchStore.environments.client === true ||
|
|
searchStore.openSource === true ||
|
|
searchStore.activeVersions.length > 0
|
|
)
|
|
"
|
|
@click="handleReset"
|
|
><ClearIcon />Clear Filters</Button
|
|
>
|
|
<div class="categories">
|
|
<h2>Categories</h2>
|
|
<div
|
|
v-for="category in categories.filter((cat) => cat.project_type === 'modpack')"
|
|
:key="category.name"
|
|
>
|
|
<SearchFilter
|
|
:active-filters="searchStore.facets"
|
|
:icon="category.icon"
|
|
:display-name="category.name"
|
|
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
|
class="filter-checkbox"
|
|
@toggle="toggleFacet"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="loaders">
|
|
<h2>Loaders</h2>
|
|
<div
|
|
v-for="loader in loaders.filter((l) => l.supported_project_types?.includes('modpack'))"
|
|
:key="loader"
|
|
>
|
|
<SearchFilter
|
|
:active-filters="searchStore.orFacets"
|
|
:icon="loader.icon"
|
|
:display-name="loader.name"
|
|
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
|
class="filter-checkbox"
|
|
@toggle="toggleOrFacet"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="environment">
|
|
<h2>Environments</h2>
|
|
<SearchFilter
|
|
v-model="searchStore.environments.client"
|
|
display-name="Client"
|
|
facet-name="client"
|
|
class="filter-checkbox"
|
|
@click="getSearchResults"
|
|
>
|
|
<ClientIcon aria-hidden="true" />
|
|
</SearchFilter>
|
|
<SearchFilter
|
|
v-model="searchStore.environments.server"
|
|
display-name="Server"
|
|
facet-name="server"
|
|
class="filter-checkbox"
|
|
@click="getSearchResults"
|
|
>
|
|
<ServerIcon aria-hidden="true" />
|
|
</SearchFilter>
|
|
</div>
|
|
<div class="versions">
|
|
<h2>Minecraft versions</h2>
|
|
<Checkbox v-model="showSnapshots" class="filter-checkbox">Show snapshots</Checkbox>
|
|
<multiselect
|
|
v-model="searchStore.activeVersions"
|
|
:options="
|
|
showSnapshots
|
|
? availableGameVersions.map((x) => x.version)
|
|
: availableGameVersions
|
|
.filter((it) => it.version_type === 'release')
|
|
.map((x) => x.version)
|
|
"
|
|
:multiple="true"
|
|
:searchable="true"
|
|
:show-no-results="false"
|
|
:close-on-select="false"
|
|
:clear-search-on-select="false"
|
|
:show-labels="false"
|
|
placeholder="Choose versions..."
|
|
@update:model-value="getSearchResults"
|
|
/>
|
|
</div>
|
|
<div class="open-source">
|
|
<h2>Open source</h2>
|
|
<Checkbox
|
|
v-model="searchStore.openSource"
|
|
class="filter-checkbox"
|
|
@click="getSearchResults"
|
|
>
|
|
Open source
|
|
</Checkbox>
|
|
</div>
|
|
</aside>
|
|
<div class="search">
|
|
<Card class="search-panel-container">
|
|
<div class="search-panel">
|
|
<div class="iconified-input">
|
|
<SearchIcon aria-hidden="true" />
|
|
<input
|
|
v-model="searchStore.searchInput"
|
|
type="text"
|
|
placeholder="Search modpacks..."
|
|
@input="getSearchResults"
|
|
/>
|
|
</div>
|
|
<span>Sort by</span>
|
|
<DropdownSelect
|
|
v-model="searchStore.filter"
|
|
name="Sort dropdown"
|
|
:options="[
|
|
'Relevance',
|
|
'Download count',
|
|
'Follow count',
|
|
'Recently published',
|
|
'Recently updated',
|
|
]"
|
|
class="sort-dropdown"
|
|
@change="getSearchResults"
|
|
/>
|
|
<span>Show per page</span>
|
|
<DropdownSelect
|
|
v-model="searchStore.limit"
|
|
name="Limit dropdown"
|
|
:options="[5, 10, 15, 20, 50, 100]"
|
|
:default-value="searchStore.limit.toString()"
|
|
:model-value="searchStore.limit.toString()"
|
|
class="limit-dropdown"
|
|
@change="getSearchResults"
|
|
/>
|
|
</div>
|
|
</Card>
|
|
<Pagination
|
|
:page="searchStore.currentPage"
|
|
:count="searchStore.pageCount"
|
|
@switch-page="switchPage"
|
|
/>
|
|
<AnimatedLogo v-if="loading" class="loading" />
|
|
<section v-else class="project-list display-mode--list instance-results" role="list">
|
|
<ProjectCard
|
|
v-for="result in searchStore.searchResults"
|
|
:id="`${result?.project_id}/`"
|
|
:key="result?.project_id"
|
|
class="result-project-item"
|
|
:type="result?.project_type"
|
|
:name="result?.title"
|
|
:description="result?.description"
|
|
:icon-url="result?.icon_url"
|
|
:downloads="result?.downloads?.toString()"
|
|
:follows="result?.follows?.toString()"
|
|
:created-at="result?.date_created"
|
|
:updated-at="result?.date_modified"
|
|
:categories="[
|
|
...categories.filter(
|
|
(cat) =>
|
|
result?.display_categories.includes(cat.name) && cat.project_type === 'modpack'
|
|
),
|
|
...loaders.filter(
|
|
(loader) =>
|
|
result?.display_categories.includes(loader.name) &&
|
|
loader.supported_project_types?.includes('modpack')
|
|
),
|
|
]"
|
|
:project-type-display="result?.project_type"
|
|
project-type-url="project"
|
|
:server-side="result?.server_side"
|
|
:client-side="result?.client_side"
|
|
:show-updated-date="false"
|
|
:color="result?.color"
|
|
/>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
|
<style lang="scss">
|
|
.search-panel-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
margin-top: 1rem;
|
|
padding: 0.8rem !important;
|
|
|
|
.search-panel {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-evenly;
|
|
width: 100%;
|
|
gap: 1rem;
|
|
margin: 1rem auto;
|
|
white-space: nowrap;
|
|
|
|
.sort-dropdown {
|
|
min-width: 12.18rem;
|
|
}
|
|
|
|
.limit-dropdown {
|
|
width: 5rem;
|
|
}
|
|
|
|
.iconified-input {
|
|
width: 75%;
|
|
|
|
input {
|
|
flex-basis: initial;
|
|
}
|
|
}
|
|
}
|
|
|
|
.filter-panel {
|
|
button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-evenly;
|
|
|
|
svg {
|
|
margin-right: 0.4rem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.search-container {
|
|
display: flex;
|
|
|
|
.filter-panel {
|
|
position: fixed;
|
|
width: 16rem;
|
|
background: var(--color-raised-bg);
|
|
padding: 1rem 1rem 3rem 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
height: fit-content;
|
|
max-height: 100%;
|
|
overflow-y: auto;
|
|
|
|
h2 {
|
|
color: var(--color-contrast);
|
|
margin-top: 1rem;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1.16rem;
|
|
}
|
|
|
|
.filter-checkbox {
|
|
margin-bottom: 0.3rem;
|
|
font-size: 1rem;
|
|
text-transform: capitalize;
|
|
|
|
svg {
|
|
display: flex;
|
|
align-self: center;
|
|
justify-self: center;
|
|
}
|
|
|
|
button.checkbox {
|
|
border: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.search {
|
|
margin: 0 1rem 0 17rem;
|
|
width: 100%;
|
|
|
|
.loading {
|
|
margin: 2rem;
|
|
text-align: center;
|
|
}
|
|
}
|
|
}
|
|
</style>
|