Starts adding search pages to launcher. (#49)

* launcher base gui initial

* Bootstraps router, Omorphia, and prettier.

* Adds pages. Adds Vuex. SideBar nav contains user section and pages section.

* Adds Instance markup. Instances added to Home page.

* Adds News to home page.

* Adds settings to nav. Other touches.

* Polishing initial base GUI.

* Moves some styling to assets. Changes px values to rem.

* Removes pointless border-radius CSS.

* Implements Omorphia vars.

* Adds trending mods section.

* Updates home page.

* Swaps Vuex implementation for Pinia.

* Fixes invalid CSS on instance list item hover.

* Adds @ path resolve for imports.

* First pass on search page.

* Fix some styling of row display

* Cleaning up styles and markup.

* Fixes overall layout issues.

* Cleans up more styling. Modifies AppBar coloring.

* Allows pagination arrows to conditionally appear in RowDisplay.

* Adds paging behavior in RowDisplay.

* Updates nav and settings button styling.

* Brings in Knossos style for trending mods. Polishes News CSS.

* Updates Omorphia. Starts addressing PR comments.

* Addresses some more PR comments.

* Changes side navigation styling. Active route class implemented.

* Combines trending and popular row.

* Makes images more realistic. Adds CTA to instances.

* Converts all instances to card style. Converts more styles to rem.

* Moves Navigation and UserSection into App.vue

* Adds Modrinth favicon.

* Cleans up branch after merge.

* Removes unused styling.

* Adds transition to news card.

* Adds ofetch. Separates stores. More logic moved to instance store. Browse hits API.

* Modifies Browse instance styling. Moves Browse results out of Instance.vue.

* First pass on filtering.

* Points search at prod API.

* Updates Omorphia package. Adds index sorting.

* Fills out search functionality.

* Renames state files. Moves SearchPanel into Browse. Fixes checkbox styling.

* Changes how facets are composed. Dynamically sets loaders and categories.

* Moves search state to searchStore. Cleans up some code.

* Ups h2 font-size. Wraps search panel in Card.

* Cleans up branch after merge. Fixes some Browse styling.

* Search store produces query string. API call made in Browse.

* Changes filter-panel styling.

* Uses client and server icons directly. Removes dead code from search store.

* Clear button disabled on initial state. Accesses store directly, removes some dead code. Fixes search panel styling.

* Generates proj tags in Browse. Removes getter in search store.

* Removes unnecessary code.

* Reworks facet management. Fixes some styling.

* Relabels Tauri calls in tags.js. Attempts to call helper in Browse.

* fixed win10 stack overflow

* cargo fmt

* Makes computed value. Gets tags from Tauri. Overrides Omorphia style. Fixes dropdown width.

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: CodexAdrian <83074853+CodexAdrian@users.noreply.github.com>
Co-authored-by: Wyatt Verchere <wverchere@gmail.com>
This commit is contained in:
Zach Baird
2023-04-05 10:18:04 -04:00
committed by GitHub
parent b0c830119b
commit 8169d3ad49
17 changed files with 915 additions and 281 deletions

View File

@@ -11,19 +11,17 @@ import {
SettingsIcon,
Avatar,
} from 'omorphia'
import { useTheming, useInstances } from '@/store/state'
import { useTheming } from '@/store/state'
import { toggleTheme } from '@/helpers/theme'
const route = useRoute()
const router = useRouter()
const theme = useTheming()
const instances = useInstances()
instances.fetchInstances()
const themeStore = useTheming()
toggleTheme(theme.darkTheme)
toggleTheme(themeStore.darkTheme)
watch(theme, (newState) => {
watch(themeStore, (newState) => {
toggleTheme(newState.darkTheme)
})
</script>
@@ -59,7 +57,9 @@ watch(theme, (newState) => {
</section>
</div>
<div class="router-view">
<RouterView />
<Suspense>
<RouterView />
</Suspense>
</div>
</div>
</div>

View File

@@ -63,8 +63,8 @@ const handleRightPage = () => {
<p>{{ props.label }}</p>
<hr aria-hidden="true" />
<div v-if="allowPagination" class="pagination">
<ChevronLeftIcon @click="handleLeftPage" />
<ChevronRightIcon @click="handleRightPage" />
<ChevronLeftIcon role="button" @click="handleLeftPage" />
<ChevronRightIcon role="button" @click="handleRightPage" />
</div>
</div>
<section v-if="shouldRenderNormalInstances" ref="modsRow" class="instances">

View File

@@ -12,17 +12,17 @@ export async function get_tag_bundle() {
// Gets cached category tags
export async function get_categories() {
return await invoke('tags_get_categories')
return await invoke('tags_get_category_tags')
}
// Gets cached loaders tags
export async function get_loaders() {
return await invoke('tags_get_loaders')
return await invoke('tags_get_loader_tags')
}
// Gets cached game_versions tags
export async function get_game_versions() {
return await invoke('tags_get_game_versions')
return await invoke('tags_get_game_version_tags')
}
// Gets cached licenses tags

View File

@@ -1,7 +1,536 @@
<script setup></script>
<script setup>
import { ref, computed } from 'vue'
import { ofetch } from 'ofetch'
import {
Pagination,
ProjectCard,
Checkbox,
Button,
ClearIcon,
SearchIcon,
DropdownSelect,
SearchFilter,
Card,
ClientIcon,
ServerIcon,
} from 'omorphia'
import Multiselect from 'vue-multiselect'
import { useSearch } from '@/store/state'
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
// Pull search store
const searchStore = useSearch()
const selectedVersions = ref([])
const showSnapshots = ref(false)
// Sets the clear button's disabled attr
const isClearDisabled = computed({
get() {
if (searchStore.facets.length > 0) return false
if (searchStore.orFacets.length > 0) return false
if (searchStore.environments.server === true || searchStore.environments.client === true)
return false
if (searchStore.openSource === true) return false
if (selectedVersions.value.length > 0) return false
return true
},
})
const categories = await get_categories()
const loaders = await get_loaders()
const availableGameVersions = await get_game_versions()
/**
* Adds or removes facets from state
* @param {String} facet The facet to commit to state
*/
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()
}
/**
* Adds or removes orFacets from state
* @param {String} orFacet The orFacet to commit to state
*/
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()
}
/**
* Makes the API request to labrinth
*/
const getSearchResults = async () => {
const queryString = searchStore.getQueryString()
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
searchStore.setSearchResults(response)
}
await getSearchResults()
/**
* For when user enters input in search bar
*/
const refreshSearch = async () => {
await getSearchResults()
}
/**
* For when the user changes the Sort dropdown
* @param {Object} e Event param to see selected option
*/
const handleSort = async (e) => {
searchStore.filter = e.option
await getSearchResults()
}
/**
* For when user changes Limit dropdown
* @param {Object} e Event param to see selected option
*/
const handleLimit = async (e) => {
searchStore.limit = e.option
await getSearchResults()
}
/**
* For when user pages results
* @param {Number} page The new page to display
*/
const switchPage = async (page) => {
searchStore.currentPage = parseInt(page)
if (page === 1) searchStore.offset = 0
else searchStore.offset = searchStore.currentPage * 10 - 10
await getSearchResults()
}
/**
* For when a user interacts with version filters
*/
const handleVersionSelect = async () => {
searchStore.activeVersions = selectedVersions.value.map((ver) => ver)
await getSearchResults()
}
/**
* For when user resets all filters
*/
const handleReset = async () => {
searchStore.resetFilters()
selectedVersions.value = []
isClearDisabled.value = true
await getSearchResults()
}
</script>
<template>
<div>
<p>Browse page</p>
<div class="search-container">
<aside class="filter-panel">
<Button role="button" :disabled="isClearDisabled" @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="refreshSearch"
>
<ClientIcon aria-hidden="true" />
</SearchFilter>
<SearchFilter
v-model="searchStore.environments.server"
display-name="Server"
:facet-name="server"
class="filter-checkbox"
@click="refreshSearch"
>
<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="selectedVersions"
: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"
:selectable="() => selectedVersions.length <= 6"
placeholder="Choose versions..."
@update:model-value="handleVersionSelect"
/>
</div>
<div class="open-source">
<h2>Open source</h2>
<Checkbox v-model="searchStore.openSource" class="filter-checkbox" @click="refreshSearch">
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.."
@input="refreshSearch"
/>
</div>
<span>Sort by</span>
<DropdownSelect
name="Sort dropdown"
:options="[
'Relevance',
'Download count',
'Follow count',
'Recently published',
'Recently updated',
]"
:default-value="searchStore.filter"
:model-value="searchStore.filter"
class="sort-dropdown"
@change="handleSort"
/>
<span>Show per page</span>
<DropdownSelect
name="Limit dropdown"
:options="['5', '10', '15', '20', '50', '100']"
:default-value="searchStore.limit.toString()"
:model-value="searchStore.limit.toString()"
class="limit-dropdown"
@change="handleLimit"
/>
</div>
</Card>
<Pagination
:page="searchStore.currentPage"
:count="searchStore.pageCount"
@switch-page="switchPage"
/>
<section 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="instance"
:server-side="result?.server_side"
:client-side="result?.client_side"
:show-updated-date="false"
:color="result?.color"
>
</ProjectCard>
</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%;
.instance-project-item {
width: 100%;
height: auto;
cursor: pointer;
}
.result-project-item {
a {
&:hover {
text-decoration: none !important;
}
}
}
}
}
.multiselect {
color: var(--color-base) !important;
outline: 2px solid transparent;
.multiselect__input:focus-visible {
outline: none !important;
box-shadow: none !important;
padding: 0 !important;
min-height: 0 !important;
font-weight: normal !important;
margin-left: 0.5rem;
margin-bottom: 10px;
}
input {
background: transparent;
box-shadow: none;
border: none !important;
&:focus {
box-shadow: none;
}
}
input::placeholder {
color: var(--color-base);
}
.multiselect__tags {
border-radius: var(--radius-md);
background: var(--color-button-bg);
box-shadow: var(--shadow-inset-sm);
border: none;
cursor: pointer;
padding-left: 0.5rem;
font-size: 1rem;
transition: background-color 0.1s ease-in-out;
&:active {
filter: brightness(1.25);
.multiselect__spinner {
filter: brightness(1.25);
}
}
.multiselect__single {
background: transparent;
}
.multiselect__tag {
border-radius: var(--radius-md);
color: var(--color-base);
background: transparent;
border: 2px solid var(--color-brand);
}
.multiselect__tag-icon {
background: transparent;
&:after {
color: var(--color-contrast);
}
}
.multiselect__placeholder {
color: var(--color-base);
margin-left: 0.5rem;
opacity: 0.6;
font-size: 1rem;
line-height: 1.25rem;
}
}
.multiselect__content-wrapper {
background: var(--color-button-bg);
border: none;
overflow-x: hidden;
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
width: 100%;
.multiselect__element {
.multiselect__option--highlight {
background: var(--color-button-bg);
filter: brightness(1.25);
color: var(--color-contrast);
}
.multiselect__option--selected {
background: var(--color-brand);
font-weight: bold;
color: var(--color-accent-contrast);
}
}
}
.multiselect__spinner {
background: var(--color-button-bg);
&:active {
filter: brightness(1.25);
}
}
&.multiselect--disabled {
background: none;
.multiselect__current,
.multiselect__select {
background: none;
}
}
}
.multiselect--above .multiselect__content-wrapper {
border-top: none !important;
border-top-left-radius: var(--radius-md) !important;
border-top-right-radius: var(--radius-md) !important;
}
</style>

View File

@@ -2,21 +2,21 @@
import { useInstances, useNews } from '@/store/state'
import RowDisplay from '@/components/RowDisplay.vue'
const instances = useInstances()
const news = useNews()
instances.fetchInstances()
news.fetchNews()
const instanceStore = useInstances()
const newsStore = useNews()
instanceStore.fetchInstances()
newsStore.fetchNews()
// Remove once state is populated with real data
const recentInstances = instances.instances.slice(0, 4)
const popularInstances = instances.instances.filter((i) => i.downloads > 50 || i.trending)
const recentInstances = instanceStore.instances.slice(0, 4)
const popularInstances = instanceStore.instances.filter((i) => i.downloads > 50 || i.trending)
</script>
<template>
<div class="page-container">
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
<RowDisplay label="Popular packs" :instances="popularInstances" :can-paginate="true" />
<RowDisplay label="News & updates" :news="news.news" :can-paginate="true" />
<RowDisplay label="News & updates" :news="newsStore.news" :can-paginate="true" />
</div>
</template>

View File

@@ -2,10 +2,10 @@
<div class="instance-container">
<div class="side-cards">
<Card class="instance-card">
<Avatar size="lg" :src="getInstance(instances).img" />
<Avatar size="lg" :src="getInstance(instanceStore).img" />
<div class="instance-info">
<h2 class="name">{{ getInstance(instances).name }}</h2>
Fabric {{ getInstance(instances).version }}
<h2 class="name">{{ getInstance(instanceStore).name }}</h2>
Fabric {{ getInstance(instanceStore).version }}
</div>
<span class="button-group">
<Button color="primary" class="instance-button">
@@ -43,14 +43,14 @@ import { BoxIcon, SettingsIcon, FileIcon, Button, Avatar, Card, Promotion } from
import { PlayIcon, OpenFolderIcon } from '@/assets/icons'
import { useInstances } from '@/store/state'
const instances = useInstances()
instances.fetchInstances()
const instanceStore = useInstances()
instanceStore.fetchInstances()
</script>
<script>
export default {
methods: {
getInstance(instances) {
return instances.instances.find((i) => i.id === parseInt(this.$route.params.id))
getInstance(instanceStore) {
return instanceStore.instances.find((i) => i.id === parseInt(this.$route.params.id))
},
},
}

View File

@@ -0,0 +1,115 @@
import { defineStore } from 'pinia'
export const useInstances = defineStore('instanceStore', {
state: () => ({
instances: [],
}),
actions: {
fetchInstances() {
// Fetch from Tauri backend. We will repurpose this to get current instances, news, and popular packs. This action is distinct from the search action
const instances = [
{
id: 1,
name: 'Fabulously Optimized',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.18.1',
downloads: 10,
trending: true,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 2,
name: 'New Caves',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.18 ',
downloads: 8,
trending: true,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 3,
name: 'All the Mods 6',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.16.5',
downloads: 4,
trending: true,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 4,
name: 'Bees',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 5,
name: 'SkyFactory 4',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.12.2',
downloads: 1000,
trending: false,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 6,
name: 'RLCraft',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.12.2',
downloads: 10000,
trending: false,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 7,
name: 'Regrowth',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.7.10',
downloads: 1000,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 8,
name: 'Birds',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://avatars.githubusercontent.com/u/83074853?v=4',
},
{
id: 9,
name: 'Dogs',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 10,
name: 'Cats',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 11,
name: 'Rabbits',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
]
this.instances = [...instances]
},
},
})

View File

@@ -0,0 +1,35 @@
import { defineStore } from 'pinia'
export const useNews = defineStore('newsStore', {
state: () => ({ news: [] }),
actions: {
fetchNews() {
// Fetch from backend.
const news = [
{
id: 1,
headline: 'Caves & Cliffs Update: Part II Dev Q&A',
blurb: 'Your questions, answered!',
source: 'From Minecraft.Net',
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 2,
headline: 'Project of the WeeK: Gobblygook',
blurb: 'Your questions, answered!',
source: 'Modrinth Blog',
img: 'https://avatars.githubusercontent.com/t/3923733?s=280&v=4',
},
{
id: 3,
headline: 'Oreo makes a launcher',
blurb: 'What did it take?',
source: 'Modrinth Blog',
img: 'https://avatars.githubusercontent.com/u/30800863?v=4',
},
]
this.news = [...news]
},
},
})

View File

@@ -0,0 +1,116 @@
import { defineStore } from 'pinia'
export const useSearch = defineStore('searchStore', {
state: () => ({
searchResults: [],
searchInput: '',
totalHits: 0,
currentPage: 1,
pageCount: 1,
offset: 0,
filter: 'Relevance',
facets: [],
orFacets: [],
environments: {
client: false,
server: false,
},
activeVersions: [],
openSource: false,
limit: 20,
}),
actions: {
getQueryString() {
let andFacets = ['project_type:modpack']
// Iterate through possible andFacets
this.facets.forEach((facet) => {
andFacets.push(facet)
})
// Add open source to andFacets if enabled
if (this.openSource) andFacets.push('open_source:true')
// Create andFacet string
let formattedAndFacets = ''
andFacets.forEach((f) => (formattedAndFacets += `["${f}"],`))
formattedAndFacets = formattedAndFacets.slice(0, formattedAndFacets.length - 1)
formattedAndFacets += ''
// If orFacets are present, start building formatted orFacet filter
let formattedOrFacets = ''
if (this.orFacets.length > 0 || this.activeVersions.length > 0) {
formattedOrFacets += '['
// Aggregate normal orFacets
this.orFacets.forEach((orF) => (formattedOrFacets += `"${orF}",`))
// Add version list to orFacets
if (this.activeVersions.length > 0)
this.activeVersions.forEach((ver) => (formattedOrFacets += `"versions:${ver}",`))
// Add environments to orFacets if enabled
if (this.environments.client)
formattedOrFacets += '"client_side:optional","client_side:required,"'
if (this.environments.server)
formattedOrFacets += '"server_side:optional","server_side:required,"'
formattedOrFacets = formattedOrFacets.slice(0, formattedOrFacets.length - 1)
formattedOrFacets += ']'
}
// Aggregate facet query string
const facets = `&facets=[${formattedAndFacets}${
formattedOrFacets.length > 0 ? `,${formattedOrFacets}` : ''
}]`
// Configure results sorting
let indexSort
switch (this.filter) {
case 'Download count':
indexSort = 'downloads'
break
case 'Follow count':
indexSort = 'follows'
break
case 'Recently published':
indexSort = 'newest'
break
case 'Recently updated':
indexSort = 'updated'
break
default:
indexSort = 'relevance'
}
return `?query=${this.searchInput || ''}&limit=${this.limit}&offset=${this.offset || 0}${
facets || ''
}&index=${indexSort}`
},
setSearchResults(response) {
this.searchResults = [...response.hits]
this.totalHits = response.total_hits
this.offset = response.offset
this.pageCount = Math.ceil(this.totalHits / this.limit)
},
toggleCategory(cat) {
this.categories[cat] = !this.categories[cat]
},
toggleLoader(loader) {
this.loaders[loader] = !this.loaders[loader]
},
toggleEnv(env) {
this.environments[env] = !this.environments[env]
},
setVersions(versions) {
this.activeVersions = versions
},
resetFilters() {
this.facets = []
this.orFacets = []
Object.keys(this.environments).forEach((env) => {
this.environments[env] = false
})
this.activeVersions = []
this.openSource = false
},
},
})

View File

@@ -1,156 +1,6 @@
import { defineStore } from 'pinia'
import { useInstances } from './instances'
import { useSearch } from './search'
import { useTheming } from './theme'
import { useNews } from './news'
export const useTheming = defineStore('theme', {
state: () => ({ darkTheme: true }),
actions: {
toggleTheme() {
this.darkTheme = !this.darkTheme
},
},
})
export const useInstances = defineStore('instances', {
state: () => ({ instances: [] }),
actions: {
fetchInstances() {
// Fetch from backend.
const instances = [
{
id: 1,
name: 'Fabulously Optimized',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.18.1',
downloads: 10,
trending: true,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 2,
name: 'New Caves',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.18 ',
downloads: 8,
trending: true,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 3,
name: 'All the Mods 6',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.16.5',
downloads: 4,
trending: true,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 4,
name: 'Bees',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 5,
name: 'SkyFactory 4',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.12.2',
downloads: 1000,
trending: false,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 6,
name: 'RLCraft',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.12.2',
downloads: 10000,
trending: false,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 7,
name: 'Regrowth',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.7.10',
downloads: 1000,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 8,
name: 'Birds',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://avatars.githubusercontent.com/u/83074853?v=4',
},
{
id: 9,
name: 'Dogs',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/user/MpxzqsyW/eb0038489a55e7e7a188a5b50462f0b10dfc1613.jpeg',
},
{
id: 10,
name: 'Cats',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://cdn.modrinth.com/data/ssUbhMkL/icon.png',
},
{
id: 11,
name: 'Rabbits',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
version: '1.15.2',
downloads: 9,
trending: false,
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
]
this.instances = [...instances]
},
},
})
export const useNews = defineStore('news', {
state: () => ({ news: [] }),
actions: {
fetchNews() {
// Fetch from backend.
const news = [
{
id: 1,
headline: 'Caves & Cliffs Update: Part II Dev Q&A',
blurb: 'Your questions, answered!',
source: 'From Minecraft.Net',
img: 'https://avatars1.githubusercontent.com/u/6166773?v=4',
},
{
id: 2,
headline: 'Project of the WeeK: Gobblygook',
blurb: 'Your questions, answered!',
source: 'Modrinth Blog',
img: 'https://avatars.githubusercontent.com/t/3923733?s=280&v=4',
},
{
id: 3,
headline: 'Oreo makes a launcher',
blurb: 'What did it take?',
source: 'Modrinth Blog',
img: 'https://avatars.githubusercontent.com/u/30800863?v=4',
},
]
this.news = [...news]
},
},
})
export { useInstances, useSearch, useTheming, useNews }

View File

@@ -0,0 +1,10 @@
import { defineStore } from 'pinia'
export const useTheming = defineStore('themeStore', {
state: () => ({ darkTheme: true }),
actions: {
toggleTheme() {
this.darkTheme = !this.darkTheme
},
},
})