You've already forked AstralRinth
forked from didirus/AstralRinth
Search UI improvements (#107)
* Base impl * Make project type selectable * Update Browse.vue * address changes * Quick create * Run linter * fix merge * Addressed changes * Installation improvements * Run lint * resourcepacks * automatic installation of dependencies * Fix bugs with search * Addressed changes * Run linter * Fixed direct install not working * Remove back to search * Update Index.vue * Addressed some changes * Shader fix * fix resetting * Update Browse.vue * Direct install from search * More improvements * Update SearchCard.vue * Card V2 * Run linter * add instance ignoring * Update Browse.vue * Finalize changes * Update SearchCard.vue * More adjustments * Fix out of order rendering * Run linter * Fix issues * Add unlisteners --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
@@ -3,7 +3,6 @@ import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { ofetch } from 'ofetch'
|
||||
import {
|
||||
Pagination,
|
||||
ProjectCard,
|
||||
Checkbox,
|
||||
Button,
|
||||
ClearIcon,
|
||||
@@ -15,21 +14,37 @@ import {
|
||||
ServerIcon,
|
||||
NavRow,
|
||||
formatCategoryHeader,
|
||||
formatCategory,
|
||||
} from 'omorphia'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { useSearch } from '@/store/state'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
||||
import { useRoute } from 'vue-router'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const searchStore = useSearch()
|
||||
searchStore.projectType = route.params.projectType
|
||||
const showVersions = ref(true)
|
||||
const showLoaders = ref(true)
|
||||
const showVersions = computed(
|
||||
() => searchStore.instanceContext === null || searchStore.ignoreInstance
|
||||
)
|
||||
const showLoaders = computed(
|
||||
() =>
|
||||
searchStore.projectType !== 'datapack' &&
|
||||
searchStore.projectType !== 'resourcepack' &&
|
||||
searchStore.projectType !== 'shader' &&
|
||||
(searchStore.instanceContext === null || searchStore.ignoreInstance)
|
||||
)
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const incompatibilityWarningModal = ref(null)
|
||||
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -40,6 +55,12 @@ const categories = ref([])
|
||||
const loaders = ref([])
|
||||
const availableGameVersions = ref([])
|
||||
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
|
||||
if (searchStore.projectType === 'modpack') {
|
||||
searchStore.instanceContext = null
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
||||
get_categories(),
|
||||
@@ -72,12 +93,6 @@ const sortedCategories = computed(() => {
|
||||
|
||||
const getSearchResults = async () => {
|
||||
const queryString = searchStore.getQueryString()
|
||||
if (searchStore.instanceContext) {
|
||||
showVersions.value = false
|
||||
showLoaders.value = !(
|
||||
searchStore.projectType === 'mod' || searchStore.projectType === 'resourcepack'
|
||||
)
|
||||
}
|
||||
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
|
||||
searchStore.setSearchResults(response)
|
||||
}
|
||||
@@ -119,131 +134,141 @@ const switchPage = async (page) => {
|
||||
watch(
|
||||
() => route.params.projectType,
|
||||
async (projectType) => {
|
||||
searchStore.projectType = projectType ?? 'modpack'
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
if (!projectType) return
|
||||
searchStore.projectType = projectType
|
||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${searchStore.projectType}` })
|
||||
await handleReset()
|
||||
await switchPage(1)
|
||||
}
|
||||
)
|
||||
|
||||
const handleInstanceSwitch = async (value) => {
|
||||
searchStore.ignoreInstance = value
|
||||
await switchPage(1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small />
|
||||
<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
|
||||
v-for="categoryList in Array.from(sortedCategories)"
|
||||
:key="categoryList[0]"
|
||||
class="categories"
|
||||
>
|
||||
<h2>{{ formatCategoryHeader(categoryList[0]) }}</h2>
|
||||
<div v-for="category in categoryList[1]" :key="category.name">
|
||||
<SearchFilter
|
||||
:active-filters="searchStore.facets"
|
||||
:icon="category.icon"
|
||||
:display-name="category.name"
|
||||
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
||||
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small>
|
||||
<template #content>
|
||||
<Checkbox
|
||||
:model-value="searchStore.ignoreInstance"
|
||||
:checked="searchStore.ignoreInstance"
|
||||
label="Unfilter loader & version"
|
||||
class="filter-checkbox"
|
||||
@toggle="toggleFacet"
|
||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
showLoaders &&
|
||||
searchStore.projectType !== 'datapack' &&
|
||||
searchStore.projectType !== 'resourcepack'
|
||||
"
|
||||
class="loaders"
|
||||
>
|
||||
<h2>Loaders</h2>
|
||||
<div
|
||||
v-for="loader in loaders.filter(
|
||||
(l) =>
|
||||
(searchStore.projectType !== 'mod' &&
|
||||
l.supported_project_types?.includes(searchStore.projectType)) ||
|
||||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
)"
|
||||
: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 v-if="searchStore.projectType !== 'datapack'" 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 v-if="showVersions" 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)
|
||||
</template>
|
||||
</Instance>
|
||||
<Card class="search-panel-card">
|
||||
<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
|
||||
)
|
||||
"
|
||||
: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"
|
||||
@click="handleReset"
|
||||
><ClearIcon />Clear Filters</Button
|
||||
>
|
||||
Open source
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div v-if="showLoaders" class="loaders">
|
||||
<h2>Loaders</h2>
|
||||
<div
|
||||
v-for="loader in loaders.filter(
|
||||
(l) =>
|
||||
(searchStore.projectType !== 'mod' &&
|
||||
l.supported_project_types?.includes(searchStore.projectType)) ||
|
||||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
)"
|
||||
:key="loader"
|
||||
>
|
||||
<SearchFilter
|
||||
:active-filters="searchStore.orFacets"
|
||||
:icon="loader.icon"
|
||||
:display-name="formatCategory(loader.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||
class="filter-checkbox"
|
||||
@toggle="toggleOrFacet"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="categoryList in Array.from(sortedCategories)"
|
||||
:key="categoryList[0]"
|
||||
class="categories"
|
||||
>
|
||||
<h2>{{ formatCategoryHeader(categoryList[0]) }}</h2>
|
||||
<div v-for="category in categoryList[1]" :key="category.name">
|
||||
<SearchFilter
|
||||
:active-filters="searchStore.facets"
|
||||
:icon="category.icon"
|
||||
:display-name="formatCategory(category.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
||||
class="filter-checkbox"
|
||||
@toggle="toggleFacet"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="searchStore.projectType !== 'datapack'" 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 v-if="showVersions" class="versions">
|
||||
<h2>Minecraft versions</h2>
|
||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
||||
<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"
|
||||
label="Open source"
|
||||
@click="getSearchResults"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="search">
|
||||
<Card class="project-type-container">
|
||||
@@ -267,16 +292,16 @@ watch(
|
||||
/>
|
||||
</Card>
|
||||
<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 ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
/>
|
||||
</div>
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="searchStore.searchInput"
|
||||
type="text"
|
||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Sort by</span>
|
||||
<DropdownSelect
|
||||
v-model="searchStore.filter"
|
||||
@@ -291,6 +316,8 @@ watch(
|
||||
class="sort-dropdown"
|
||||
@change="getSearchResults"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Show per page</span>
|
||||
<DropdownSelect
|
||||
v-model="searchStore.limit"
|
||||
@@ -310,19 +337,11 @@ watch(
|
||||
/>
|
||||
<SplashScreen v-if="loading" />
|
||||
<section v-else class="project-list display-mode--list instance-results" role="list">
|
||||
<ProjectCard
|
||||
<SearchCard
|
||||
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"
|
||||
:project="result"
|
||||
:instance="searchStore.instanceContext"
|
||||
:categories="[
|
||||
...categories.filter(
|
||||
(cat) =>
|
||||
@@ -335,16 +354,21 @@ watch(
|
||||
loader.supported_project_types?.includes(searchStore.projectType)
|
||||
),
|
||||
]"
|
||||
: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"
|
||||
:confirm-modal="confirmModal"
|
||||
:mod-install-modal="modInstallModal"
|
||||
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||
/>
|
||||
</section>
|
||||
<Pagination
|
||||
:page="searchStore.currentPage"
|
||||
:count="searchStore.pageCount"
|
||||
@switch-page="switchPage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
@@ -360,39 +384,47 @@ watch(
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.search-panel-container {
|
||||
.search-panel-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
padding: 0.8rem !important;
|
||||
background-color: var(--color-bg) !important;
|
||||
margin-bottom: 0;
|
||||
min-height: min-content !important;
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
.iconified-input {
|
||||
input {
|
||||
max-width: 20rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.search-panel-container {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding: 1rem !important;
|
||||
white-space: nowrap;
|
||||
gap: 1rem;
|
||||
|
||||
.inline-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin: 1rem auto;
|
||||
white-space: nowrap;
|
||||
gap: 0.5rem;
|
||||
|
||||
.sort-dropdown {
|
||||
min-width: 12.18rem;
|
||||
max-width: 12.25rem;
|
||||
}
|
||||
|
||||
.limit-dropdown {
|
||||
width: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
width: 75%;
|
||||
|
||||
input {
|
||||
flex-basis: initial;
|
||||
}
|
||||
}
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
@@ -413,13 +445,14 @@ watch(
|
||||
|
||||
.filter-panel {
|
||||
position: fixed;
|
||||
width: 19rem;
|
||||
width: 20rem;
|
||||
background: var(--color-raised-bg);
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
max-height: calc(100vh - 3rem);
|
||||
height: fit-content;
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
overflow-y: auto;
|
||||
|
||||
h2 {
|
||||
@@ -432,7 +465,6 @@ watch(
|
||||
.filter-checkbox {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1rem;
|
||||
text-transform: capitalize;
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
@@ -447,8 +479,8 @@ watch(
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 1rem 0 20rem;
|
||||
width: calc(100% - 21rem);
|
||||
margin: 0 1rem 0 21rem;
|
||||
width: calc(100% - 22rem);
|
||||
|
||||
.loading {
|
||||
margin: 2rem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { shallowRef } from 'vue'
|
||||
import { onUnmounted, shallowRef } from 'vue'
|
||||
import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
@@ -14,10 +14,11 @@ breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
||||
const profiles = await list(true)
|
||||
const instances = shallowRef(Object.values(profiles))
|
||||
|
||||
profile_listener(async () => {
|
||||
const unlisten = await profile_listener(async () => {
|
||||
const profiles = await list(true)
|
||||
instances.value = Object.values(profiles)
|
||||
})
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -96,12 +96,6 @@ breadcrumbs.setContext({
|
||||
link: route.path,
|
||||
})
|
||||
|
||||
profile_listener(async (event) => {
|
||||
if (event.profile_path === route.params.id) {
|
||||
instance.value = await get(route.params.id)
|
||||
}
|
||||
})
|
||||
|
||||
const uuid = ref(null)
|
||||
const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -143,11 +137,20 @@ const stopInstance = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id)
|
||||
}
|
||||
})
|
||||
|
||||
const unlistenProcesses = await process_listener((e) => {
|
||||
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
onUnmounted(() => {
|
||||
unlistenProcesses()
|
||||
unlistenProfiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -84,10 +84,9 @@ import {
|
||||
} from 'omorphia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -182,7 +181,7 @@ const updateSort = (projects, sort) => {
|
||||
}
|
||||
|
||||
const searchMod = () => {
|
||||
router.push({ path: '/browse/mod', query: { instance: route.params.id } })
|
||||
router.push({ path: '/browse/mod' })
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Card>
|
||||
<div class="markdown-body" v-html="renderHighlightedString(project.body)" />
|
||||
<div class="markdown-body" v-html="renderHighlightedString(project?.body ?? '')" />
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -187,6 +187,7 @@
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -233,6 +234,7 @@ import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { useSearch } from '@/store/search'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
|
||||
const searchStore = useSearch()
|
||||
|
||||
@@ -242,6 +244,7 @@ const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const incompatibilityWarning = ref(null)
|
||||
const instance = ref(searchStore.instanceContext)
|
||||
const installing = ref(false)
|
||||
|
||||
@@ -267,6 +270,10 @@ watch(
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const markInstalled = () => {
|
||||
installed.value = true
|
||||
}
|
||||
|
||||
async function install(version) {
|
||||
installing.value = true
|
||||
let queuedVersionData
|
||||
@@ -308,17 +315,43 @@ async function install(version) {
|
||||
: true)
|
||||
)
|
||||
if (!selectedVersion) {
|
||||
incompatibilityWarning.value.show(
|
||||
instance.value,
|
||||
data.value.title,
|
||||
versions.value,
|
||||
markInstalled
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
} else {
|
||||
queuedVersionData = selectedVersion
|
||||
await installMod(instance.value.path, selectedVersion.id)
|
||||
installVersionDependencies(instance.value, queuedVersionData)
|
||||
}
|
||||
} else {
|
||||
const gameVersion = instance.value.metadata.game_version
|
||||
const loader = instance.value.metadata.loader
|
||||
const compatible = versions.value.some(
|
||||
(v) =>
|
||||
v.game_versions.includes(gameVersion) &&
|
||||
(data.value.project_type === 'mod'
|
||||
? v.loaders.includes(loader) || v.loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
if (compatible) {
|
||||
await installMod(instance.value.path, queuedVersionData.id)
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
} else {
|
||||
incompatibilityWarning.value.show(
|
||||
instance.value,
|
||||
data.value.title,
|
||||
[queuedVersionData],
|
||||
markInstalled
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
queuedVersionData = selectedVersion
|
||||
await installMod(instance.value.path, selectedVersion.id)
|
||||
} else {
|
||||
await installMod(instance.value.path, queuedVersionData.id)
|
||||
}
|
||||
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
|
||||
installed.value = true
|
||||
} else {
|
||||
if (version) {
|
||||
@@ -345,9 +378,9 @@ async function install(version) {
|
||||
.project-sidebar {
|
||||
position: fixed;
|
||||
width: 20rem;
|
||||
min-height: 100vh;
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
height: fit-content;
|
||||
max-height: 100vh;
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
overflow-y: auto;
|
||||
background: var(--color-raised-bg);
|
||||
padding: 1rem;
|
||||
|
||||
@@ -35,14 +35,9 @@
|
||||
placeholder="Filter versions..."
|
||||
/>
|
||||
</div>
|
||||
<Checkbox
|
||||
v-model="filterCompatible"
|
||||
label="Only show compatible versions"
|
||||
class="filter-checkbox"
|
||||
/>
|
||||
<Button
|
||||
class="no-wrap clear-filters"
|
||||
:disabled="!filterLoader && !filterVersions && !filterCompatible"
|
||||
:disabled="!filterLoader && !filterVersions"
|
||||
:action="clearFilters"
|
||||
>
|
||||
<ClearIcon />
|
||||
@@ -129,28 +124,17 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
CheckIcon,
|
||||
ClearIcon,
|
||||
Badge,
|
||||
DownloadIcon,
|
||||
Checkbox,
|
||||
formatNumber,
|
||||
} from 'omorphia'
|
||||
import { Card, Button, CheckIcon, ClearIcon, Badge, DownloadIcon, formatNumber } from 'omorphia'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
import { ref } from 'vue'
|
||||
|
||||
let filterVersions = ref(null)
|
||||
let filterLoader = ref(null)
|
||||
let filterCompatible = ref(false)
|
||||
|
||||
const clearFilters = () => {
|
||||
filterVersions.value = null
|
||||
filterLoader.value = null
|
||||
filterCompatible.value = false
|
||||
}
|
||||
|
||||
defineProps({
|
||||
|
||||
Reference in New Issue
Block a user