You've already forked AstralRinth
forked from didirus/AstralRinth
Search fixes (#134)
* Search fixes * Fix small instance ui * fix javaw issue * menu fix * Add confirm modal for deletion * fix build
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, ref, readonly, shallowRef, watch } from 'vue'
|
||||
import {
|
||||
Pagination,
|
||||
Checkbox,
|
||||
Button,
|
||||
ClearIcon,
|
||||
SearchIcon,
|
||||
DropdownSelect,
|
||||
SearchFilter,
|
||||
Card,
|
||||
ClientIcon,
|
||||
@@ -15,74 +14,337 @@ import {
|
||||
formatCategoryHeader,
|
||||
formatCategory,
|
||||
Promotion,
|
||||
DropdownSelect,
|
||||
} from 'omorphia'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { handleError, useSearch } from '@/store/state'
|
||||
import { handleError } 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 { useRoute, useRouter } from 'vue-router'
|
||||
import { Avatar } from 'omorphia'
|
||||
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'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { check_installed, get as getInstance } from '@/helpers/profile.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const searchStore = useSearch()
|
||||
searchStore.projectType = route.params.projectType
|
||||
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()
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path, query: route.query })
|
||||
|
||||
const loading = ref(false)
|
||||
const query = ref('')
|
||||
const facets = ref([])
|
||||
const orFacets = ref([])
|
||||
const selectedVersions = ref([])
|
||||
const onlyOpenSource = ref(false)
|
||||
const showSnapshots = ref(false)
|
||||
const loading = ref(true)
|
||||
const selectedEnvironments = ref([])
|
||||
const sortTypes = readonly([
|
||||
{ display: 'Relevance', name: 'relevance' },
|
||||
{ display: 'Download count', name: 'downloads' },
|
||||
{ display: 'Follow count', name: 'follows' },
|
||||
{ display: 'Recently published', name: 'newest' },
|
||||
{ display: 'Recently updated', name: 'updated' },
|
||||
])
|
||||
const sortType = ref(sortTypes[0])
|
||||
const maxResults = ref(20)
|
||||
const currentPage = ref(1)
|
||||
const projectType = ref(route.params.projectType)
|
||||
const instanceContext = ref(null)
|
||||
const ignoreInstanceLoaders = ref(false)
|
||||
const ignoreInstanceGameVersions = ref(false)
|
||||
|
||||
const categories = ref([])
|
||||
const loaders = ref([])
|
||||
const availableGameVersions = ref([])
|
||||
const results = shallowRef([])
|
||||
const pageCount = computed(() =>
|
||||
results.value ? Math.ceil(results.value.total_hits / results.value.limit) : 1
|
||||
)
|
||||
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
|
||||
if (searchStore.projectType === 'modpack') {
|
||||
searchStore.instanceContext = null
|
||||
function getArrayOrString(x) {
|
||||
if (typeof x === 'string' || x instanceof String) {
|
||||
return [x]
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
||||
get_categories().catch(handleError),
|
||||
get_loaders().catch(handleError),
|
||||
get_game_versions().catch(handleError),
|
||||
])
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
if (searchStore.projectType === 'modpack') {
|
||||
searchStore.instanceContext = null
|
||||
if (route.query.iv) {
|
||||
ignoreInstanceGameVersions.value = route.query.iv === 'true'
|
||||
}
|
||||
if (route.query.il) {
|
||||
ignoreInstanceLoaders.value = route.query.il === 'true'
|
||||
}
|
||||
if (route.query.i) {
|
||||
instanceContext.value = await getInstance(route.query.i, true)
|
||||
}
|
||||
if (route.query.q) {
|
||||
query.value = route.query.q
|
||||
}
|
||||
if (route.query.f) {
|
||||
facets.value = getArrayOrString(route.query.f)
|
||||
}
|
||||
if (route.query.g) {
|
||||
orFacets.value = getArrayOrString(route.query.g)
|
||||
}
|
||||
if (route.query.v) {
|
||||
selectedVersions.value = getArrayOrString(route.query.v)
|
||||
}
|
||||
if (route.query.l) {
|
||||
onlyOpenSource.value = route.query.l === 'true'
|
||||
}
|
||||
if (route.query.h) {
|
||||
showSnapshots.value = route.query.h === 'true'
|
||||
}
|
||||
if (route.query.e) {
|
||||
selectedEnvironments.value = getArrayOrString(route.query.e)
|
||||
}
|
||||
if (route.query.s) {
|
||||
sortType.value.name = route.query.s
|
||||
|
||||
switch (sortType.value.name) {
|
||||
case 'relevance':
|
||||
sortType.value.display = 'Relevance'
|
||||
break
|
||||
case 'downloads':
|
||||
sortType.value.display = 'Downloads'
|
||||
break
|
||||
case 'newest':
|
||||
sortType.value.display = 'Recently published'
|
||||
break
|
||||
case 'updated':
|
||||
sortType.value.display = 'Recently updated'
|
||||
break
|
||||
case 'follows':
|
||||
sortType.value.display = 'Follow count'
|
||||
break
|
||||
}
|
||||
searchStore.searchInput = ''
|
||||
await handleReset()
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
if (route.query.m) {
|
||||
maxResults.value = route.query.m
|
||||
}
|
||||
if (route.query.o) {
|
||||
currentPage.value = Math.ceil(route.query.o / maxResults.value) + 1
|
||||
}
|
||||
|
||||
async function refreshSearch() {
|
||||
const base = 'https://api.modrinth.com/v2/'
|
||||
|
||||
const params = [`limit=${maxResults.value}`, `index=${sortType.value.name}`]
|
||||
|
||||
if (query.value.length > 0) {
|
||||
params.push(`query=${query.value.replace(/ /g, '+')}`)
|
||||
}
|
||||
|
||||
if (instanceContext.value) {
|
||||
if (!ignoreInstanceLoaders.value && projectType.value === 'mod') {
|
||||
orFacets.value = [`categories:${encodeURIComponent(instanceContext.value.metadata.loader)}`]
|
||||
}
|
||||
|
||||
if (!ignoreInstanceGameVersions.value) {
|
||||
selectedVersions.value = [instanceContext.value.metadata.game_version]
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
facets.value.length > 0 ||
|
||||
orFacets.value.length > 0 ||
|
||||
selectedVersions.value.length > 0 ||
|
||||
selectedEnvironments.value.length > 0 ||
|
||||
projectType.value
|
||||
) {
|
||||
let formattedFacets = []
|
||||
for (const facet of facets.value) {
|
||||
formattedFacets.push([facet])
|
||||
}
|
||||
|
||||
// loaders specifier
|
||||
if (orFacets.value.length > 0) {
|
||||
formattedFacets.push(orFacets.value)
|
||||
} else if (projectType.value === 'mod') {
|
||||
formattedFacets.push(
|
||||
['forge', 'fabric', 'quilt'].map((x) => `categories:'${encodeURIComponent(x)}'`)
|
||||
)
|
||||
} else if (projectType.value === 'datapack') {
|
||||
formattedFacets.push(['datapack'].map((x) => `categories:'${encodeURIComponent(x)}'`))
|
||||
}
|
||||
|
||||
if (selectedVersions.value.length > 0) {
|
||||
const versionFacets = []
|
||||
for (const facet of selectedVersions.value) {
|
||||
versionFacets.push('versions:' + facet)
|
||||
}
|
||||
formattedFacets.push(versionFacets)
|
||||
}
|
||||
|
||||
if (onlyOpenSource.value) {
|
||||
formattedFacets.push(['open_source:true'])
|
||||
}
|
||||
|
||||
if (selectedEnvironments.value.length > 0) {
|
||||
let environmentFacets = []
|
||||
|
||||
const includesClient = selectedEnvironments.value.includes('client')
|
||||
const includesServer = selectedEnvironments.value.includes('server')
|
||||
if (includesClient && includesServer) {
|
||||
environmentFacets = [['client_side:required'], ['server_side:required']]
|
||||
} else {
|
||||
if (includesClient) {
|
||||
environmentFacets = [
|
||||
['client_side:optional', 'client_side:required'],
|
||||
['server_side:optional', 'server_side:unsupported'],
|
||||
]
|
||||
}
|
||||
if (includesServer) {
|
||||
environmentFacets = [
|
||||
['client_side:optional', 'client_side:unsupported'],
|
||||
['server_side:optional', 'server_side:required'],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
formattedFacets = [...formattedFacets, ...environmentFacets]
|
||||
}
|
||||
|
||||
if (projectType.value) {
|
||||
formattedFacets.push([
|
||||
`project_type:${projectType.value === 'datapack' ? 'mod' : projectType.value}`,
|
||||
])
|
||||
}
|
||||
|
||||
params.push(`facets=${JSON.stringify(formattedFacets)}`)
|
||||
}
|
||||
|
||||
const offset = (currentPage.value - 1) * maxResults.value
|
||||
if (currentPage.value !== 1) {
|
||||
params.push(`offset=${offset}`)
|
||||
}
|
||||
|
||||
let url = 'search'
|
||||
|
||||
if (params.length > 0) {
|
||||
for (let i = 0; i < params.length; i++) {
|
||||
url += i === 0 ? `?${params[i]}` : `&${params[i]}`
|
||||
}
|
||||
}
|
||||
|
||||
let val = `${base}${url}`
|
||||
|
||||
const rawResults = await useFetch(val, 'search results')
|
||||
if (instanceContext.value) {
|
||||
for (let val of rawResults.hits) {
|
||||
val.installed = await check_installed(instanceContext.value.path, val.project_id).then(
|
||||
(x) => (val.installed = x)
|
||||
)
|
||||
}
|
||||
}
|
||||
results.value = rawResults
|
||||
}
|
||||
|
||||
async function onSearchChange(newPageNumber) {
|
||||
currentPage.value = newPageNumber
|
||||
|
||||
if (query.value === null) {
|
||||
return
|
||||
}
|
||||
|
||||
await refreshSearch()
|
||||
|
||||
const obj = getSearchUrl((currentPage.value - 1) * maxResults.value, true)
|
||||
await router.replace({ path: route.path, query: obj })
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path, query: obj })
|
||||
}
|
||||
|
||||
const searchWrapper = ref(null)
|
||||
async function onSearchChangeToTop(newPageNumber) {
|
||||
await onSearchChange(newPageNumber)
|
||||
await nextTick()
|
||||
searchWrapper.value.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
function getSearchUrl(offset, useObj) {
|
||||
const queryItems = []
|
||||
const obj = {}
|
||||
|
||||
if (query.value) {
|
||||
queryItems.push(`q=${encodeURIComponent(query.value)}`)
|
||||
obj.q = query.value
|
||||
}
|
||||
if (offset > 0) {
|
||||
queryItems.push(`o=${offset}`)
|
||||
obj.o = offset
|
||||
}
|
||||
if (facets.value.length > 0) {
|
||||
queryItems.push(`f=${encodeURIComponent(facets.value)}`)
|
||||
obj.f = facets.value
|
||||
}
|
||||
if (orFacets.value.length > 0) {
|
||||
queryItems.push(`g=${encodeURIComponent(orFacets.value)}`)
|
||||
obj.g = orFacets.value
|
||||
}
|
||||
if (selectedVersions.value.length > 0) {
|
||||
queryItems.push(`v=${encodeURIComponent(selectedVersions.value)}`)
|
||||
obj.v = selectedVersions.value
|
||||
}
|
||||
if (onlyOpenSource.value) {
|
||||
queryItems.push('l=true')
|
||||
obj.l = true
|
||||
}
|
||||
if (showSnapshots.value) {
|
||||
queryItems.push('h=true')
|
||||
obj.h = true
|
||||
}
|
||||
if (selectedEnvironments.value.length > 0) {
|
||||
queryItems.push(`e=${encodeURIComponent(selectedEnvironments.value)}`)
|
||||
obj.e = selectedEnvironments.value
|
||||
}
|
||||
if (sortType.value.name !== 'relevance') {
|
||||
queryItems.push(`s=${encodeURIComponent(sortType.value.name)}`)
|
||||
obj.s = sortType.value.name
|
||||
}
|
||||
if (maxResults.value !== 20) {
|
||||
queryItems.push(`m=${encodeURIComponent(maxResults.value)}`)
|
||||
obj.m = maxResults.value
|
||||
}
|
||||
if (instanceContext.value) {
|
||||
queryItems.push(`i=${encodeURIComponent(instanceContext.value.path)}`)
|
||||
obj.i = instanceContext.value.path
|
||||
}
|
||||
if (ignoreInstanceGameVersions.value) {
|
||||
queryItems.push('iv=true')
|
||||
obj.iv = true
|
||||
}
|
||||
if (ignoreInstanceLoaders.value) {
|
||||
queryItems.push('il=true')
|
||||
obj.il = true
|
||||
}
|
||||
|
||||
let url = `${route.path}`
|
||||
|
||||
if (queryItems.length > 0) {
|
||||
url += `?${queryItems[0]}`
|
||||
|
||||
for (let i = 1; i < queryItems.length; i++) {
|
||||
url += `&${queryItems[i]}`
|
||||
}
|
||||
}
|
||||
|
||||
return useObj ? obj : url
|
||||
}
|
||||
|
||||
const sortedCategories = computed(() => {
|
||||
const values = new Map()
|
||||
for (const category of categories.value.filter(
|
||||
(cat) =>
|
||||
cat.project_type ===
|
||||
(searchStore.projectType === 'datapack' ? 'mod' : searchStore.projectType)
|
||||
(cat) => cat.project_type === (projectType.value === 'datapack' ? 'mod' : projectType.value)
|
||||
)) {
|
||||
if (!values.has(category.header)) {
|
||||
values.set(category.header, [])
|
||||
@@ -92,128 +354,188 @@ const sortedCategories = computed(() => {
|
||||
return values
|
||||
})
|
||||
|
||||
const getSearchResults = async () => {
|
||||
const queryString = searchStore.getQueryString()
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search${queryString}`,
|
||||
'search results'
|
||||
)
|
||||
searchStore.setSearchResults(response)
|
||||
async function clearFilters() {
|
||||
for (const facet of [...facets.value]) {
|
||||
await toggleFacet(facet, true)
|
||||
}
|
||||
for (const facet of [...orFacets.value]) {
|
||||
await toggleOrFacet(facet, true)
|
||||
}
|
||||
|
||||
onlyOpenSource.value = false
|
||||
selectedVersions.value = []
|
||||
selectedEnvironments.value = []
|
||||
await onSearchChange(1)
|
||||
}
|
||||
|
||||
const handleReset = async () => {
|
||||
searchStore.currentPage = 1
|
||||
searchStore.offset = 0
|
||||
searchStore.resetFilters()
|
||||
await getSearchResults()
|
||||
async function toggleFacet(elementName, doNotSendRequest = false) {
|
||||
const index = facets.value.indexOf(elementName)
|
||||
|
||||
if (index !== -1) {
|
||||
facets.value.splice(index, 1)
|
||||
} else {
|
||||
facets.value.push(elementName)
|
||||
}
|
||||
|
||||
if (!doNotSendRequest) {
|
||||
await onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleFacet = async (facet) => {
|
||||
searchStore.currentPage = 1
|
||||
searchStore.offset = 0
|
||||
const index = searchStore.facets.indexOf(facet)
|
||||
async function toggleOrFacet(elementName, doNotSendRequest) {
|
||||
const index = orFacets.value.indexOf(elementName)
|
||||
if (index !== -1) {
|
||||
orFacets.value.splice(index, 1)
|
||||
} else {
|
||||
orFacets.value.push(elementName)
|
||||
}
|
||||
|
||||
if (index !== -1) searchStore.facets.splice(index, 1)
|
||||
else searchStore.facets.push(facet)
|
||||
|
||||
await switchPage(1)
|
||||
if (!doNotSendRequest) {
|
||||
await onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleOrFacet = async (orFacet) => {
|
||||
const index = searchStore.orFacets.indexOf(orFacet)
|
||||
function toggleEnv(environment, sendRequest) {
|
||||
const index = selectedEnvironments.value.indexOf(environment)
|
||||
if (index !== -1) {
|
||||
selectedEnvironments.value.splice(index, 1)
|
||||
} else {
|
||||
selectedEnvironments.value.push(environment)
|
||||
}
|
||||
|
||||
if (index !== -1) searchStore.orFacets.splice(index, 1)
|
||||
else searchStore.orFacets.push(orFacet)
|
||||
|
||||
await switchPage(1)
|
||||
}
|
||||
|
||||
const switchPage = async (page) => {
|
||||
searchStore.currentPage = parseInt(page)
|
||||
if (page === 1) searchStore.offset = 0
|
||||
else searchStore.offset = (searchStore.currentPage - 1) * searchStore.limit
|
||||
await getSearchResults()
|
||||
if (!sendRequest) {
|
||||
onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.projectType,
|
||||
async (projectType) => {
|
||||
if (!projectType) return
|
||||
searchStore.projectType = projectType
|
||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${searchStore.projectType}` })
|
||||
await handleReset()
|
||||
await switchPage(1)
|
||||
async (newType) => {
|
||||
if (!newType) return
|
||||
projectType.value = newType
|
||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${projectType.value}` })
|
||||
|
||||
sortType.value = { display: 'Relevance', name: 'relevance' }
|
||||
query.value = ''
|
||||
|
||||
loading.value = true
|
||||
await clearFilters()
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
|
||||
const handleInstanceSwitch = async (value) => {
|
||||
searchStore.ignoreInstance = value
|
||||
await switchPage(1)
|
||||
}
|
||||
const [categories, loaders, availableGameVersions] = await Promise.all([
|
||||
get_categories().catch(handleError).then(ref),
|
||||
get_loaders().catch(handleError).then(ref),
|
||||
get_game_versions().catch(handleError).then(ref),
|
||||
refreshSearch(),
|
||||
])
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const values = [
|
||||
{ label: 'Data Packs', href: `/browse/datapack` },
|
||||
{ label: 'Shaders', href: `/browse/shader` },
|
||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||
]
|
||||
|
||||
if (searchStore.instanceContext) {
|
||||
if (searchStore.instanceContext.metadata.loader !== 'vanilla') {
|
||||
if (instanceContext.value) {
|
||||
if (
|
||||
availableGameVersions.value.findIndex(
|
||||
(x) => x.version === instanceContext.value.metadata.game_version
|
||||
) <= availableGameVersions.value.findIndex((x) => x.version === '1.13')
|
||||
) {
|
||||
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||
}
|
||||
|
||||
if (instanceContext.value.metadata.loader !== 'vanilla') {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
}
|
||||
} else {
|
||||
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
||||
}
|
||||
|
||||
return values
|
||||
})
|
||||
|
||||
const showVersions = computed(
|
||||
() => instanceContext.value === null || ignoreInstanceGameVersions.value
|
||||
)
|
||||
const showLoaders = computed(
|
||||
() =>
|
||||
(projectType.value !== 'datapack' &&
|
||||
projectType.value !== 'resourcepack' &&
|
||||
projectType.value !== 'shader' &&
|
||||
instanceContext.value === null) ||
|
||||
ignoreInstanceLoaders.value
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small>
|
||||
<template #content>
|
||||
<Checkbox
|
||||
:model-value="searchStore.ignoreInstance"
|
||||
:checked="searchStore.ignoreInstance"
|
||||
label="Show unsupported content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||
<div v-if="instanceContext" class="small-instance">
|
||||
<div class="instance">
|
||||
<Avatar
|
||||
:src="
|
||||
!instanceContext.metadata.icon ||
|
||||
(instanceContext.metadata.icon && instanceContext.metadata.icon.startsWith('http'))
|
||||
? instanceContext.metadata.icon
|
||||
: convertFileSrc(instanceContext.metadata.icon)
|
||||
"
|
||||
:alt="instanceContext.metadata.name"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Instance>
|
||||
<div class="small-instance_info">
|
||||
<span class="title">{{ instanceContext.metadata.name }}</span>
|
||||
<span>
|
||||
{{
|
||||
instanceContext.metadata.loader.charAt(0).toUpperCase() +
|
||||
instanceContext.metadata.loader.slice(1)
|
||||
}}
|
||||
{{ instanceContext.metadata.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Checkbox
|
||||
v-model="ignoreInstanceGameVersions"
|
||||
label="Override game versions"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="onSearchChangeToTop(1)"
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="ignoreInstanceLoaders"
|
||||
label="Override loaders"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="onSearchChangeToTop(1)"
|
||||
/>
|
||||
</div>
|
||||
<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
|
||||
)
|
||||
onlyOpenSource === false &&
|
||||
selectedEnvironments.length === 0 &&
|
||||
selectedVersions.length === 0 &&
|
||||
facets.length === 0 &&
|
||||
orFacets.length === 0
|
||||
"
|
||||
@click="handleReset"
|
||||
><ClearIcon />Clear Filters</Button
|
||||
@click="clearFilters"
|
||||
>
|
||||
<ClearIcon /> Clear Filters
|
||||
</Button>
|
||||
<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))
|
||||
(projectType !== 'mod' && l.supported_project_types?.includes(projectType)) ||
|
||||
(projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
)"
|
||||
:key="loader"
|
||||
>
|
||||
<SearchFilter
|
||||
:active-filters="searchStore.orFacets"
|
||||
:active-filters="orFacets"
|
||||
:icon="loader.icon"
|
||||
:display-name="formatCategory(loader.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||
@@ -222,49 +544,11 @@ const selectableProjectTypes = computed(() => {
|
||||
/>
|
||||
</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"
|
||||
v-model="selectedVersions"
|
||||
:options="
|
||||
showSnapshots
|
||||
? availableGameVersions.map((x) => x.version)
|
||||
@@ -279,21 +563,59 @@ const selectableProjectTypes = computed(() => {
|
||||
:clear-search-on-select="false"
|
||||
:show-labels="false"
|
||||
placeholder="Choose versions..."
|
||||
@update:model-value="getSearchResults"
|
||||
@update:model-value="onSearchChange(1)"
|
||||
/>
|
||||
</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="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="projectType !== 'datapack'" class="environment">
|
||||
<h2>Environments</h2>
|
||||
<SearchFilter
|
||||
:active-filters="selectedEnvironments"
|
||||
display-name="Client"
|
||||
facet-name="client"
|
||||
class="filter-checkbox"
|
||||
@toggle="toggleEnv"
|
||||
>
|
||||
<ClientIcon aria-hidden="true" />
|
||||
</SearchFilter>
|
||||
<SearchFilter
|
||||
:active-filters="selectedEnvironments"
|
||||
display-name="Server"
|
||||
facet-name="server"
|
||||
class="filter-checkbox"
|
||||
@toggle="toggleEnv"
|
||||
>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
</SearchFilter>
|
||||
</div>
|
||||
<div class="open-source">
|
||||
<h2>Open source</h2>
|
||||
<Checkbox
|
||||
v-model="searchStore.openSource"
|
||||
v-model="onlyOpenSource"
|
||||
label="Open source only"
|
||||
class="filter-checkbox"
|
||||
label="Open source"
|
||||
@click="getSearchResults"
|
||||
@update:model-value="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="search">
|
||||
<div ref="searchWrapper" class="search">
|
||||
<Promotion class="promotion" />
|
||||
<Card class="project-type-container">
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
@@ -302,64 +624,59 @@ const selectableProjectTypes = computed(() => {
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="searchStore.searchInput"
|
||||
v-model="query"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
:placeholder="`Search ${projectType}s...`"
|
||||
@input="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<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"
|
||||
v-model="sortType"
|
||||
name="Sort by"
|
||||
:options="sortTypes"
|
||||
:display-name="(option) => option?.display"
|
||||
@change="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Show per page</span>
|
||||
<DropdownSelect
|
||||
v-model="searchStore.limit"
|
||||
name="Limit dropdown"
|
||||
v-model="maxResults"
|
||||
name="Max results"
|
||||
:options="[5, 10, 15, 20, 50, 100]"
|
||||
:default-value="searchStore.limit.toString()"
|
||||
:model-value="searchStore.limit.toString()"
|
||||
:default-value="maxResults"
|
||||
:model-value="maxResults"
|
||||
class="limit-dropdown"
|
||||
@change="getSearchResults"
|
||||
@change="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Pagination
|
||||
:page="searchStore.currentPage"
|
||||
:count="searchStore.pageCount"
|
||||
@switch-page="switchPage"
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||
class="pagination-before"
|
||||
@switch-page="onSearchChange"
|
||||
/>
|
||||
<SplashScreen v-if="loading" />
|
||||
<section v-else class="project-list display-mode--list instance-results" role="list">
|
||||
<SearchCard
|
||||
v-for="result in searchStore.searchResults"
|
||||
v-for="result in results.hits"
|
||||
:key="result?.project_id"
|
||||
:project="result"
|
||||
:instance="searchStore.instanceContext"
|
||||
:instance="instanceContext"
|
||||
:categories="[
|
||||
...categories.filter(
|
||||
(cat) =>
|
||||
result?.display_categories.includes(cat.name) &&
|
||||
cat.project_type === searchStore.projectType
|
||||
result?.display_categories.includes(cat.name) && cat.project_type === projectType
|
||||
),
|
||||
...loaders.filter(
|
||||
(loader) =>
|
||||
result?.display_categories.includes(loader.name) &&
|
||||
loader.supported_project_types?.includes(searchStore.projectType)
|
||||
loader.supported_project_types?.includes(projectType)
|
||||
),
|
||||
]"
|
||||
:confirm-modal="confirmModal"
|
||||
@@ -367,10 +684,12 @@ const selectableProjectTypes = computed(() => {
|
||||
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||
/>
|
||||
</section>
|
||||
<Pagination
|
||||
:page="searchStore.currentPage"
|
||||
:count="searchStore.pageCount"
|
||||
@switch-page="switchPage"
|
||||
<pagination
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||
class="pagination-after"
|
||||
@switch-page="onSearchChangeToTop"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,6 +700,32 @@ const selectableProjectTypes = computed(() => {
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<style lang="scss">
|
||||
.small-instance {
|
||||
background: var(--color-bg);
|
||||
padding: var(--gap-lg);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--gap-md);
|
||||
|
||||
.instance {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.small-instance_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-checkbox {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1rem;
|
||||
@@ -492,7 +837,8 @@ const selectableProjectTypes = computed(() => {
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 1rem 0 21rem;
|
||||
scroll-behavior: smooth;
|
||||
margin: 0 1rem 0.5rem 21rem;
|
||||
width: calc(100% - 22rem);
|
||||
|
||||
.loading {
|
||||
|
||||
@@ -36,7 +36,6 @@ const getInstances = async () => {
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
console.log(filter.value)
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'featured modpacks'
|
||||
|
||||
@@ -21,35 +21,52 @@ fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).jo
|
||||
const settings = ref(fetchSettings)
|
||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
||||
|
||||
watch(settings.value, async (oldSettings, newSettings) => {
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
watch(
|
||||
settings,
|
||||
async (oldSettings, newSettings) => {
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
|
||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||
setSettings.java_globals.JAVA_8 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_17?.path === '') {
|
||||
setSettings.java_globals.JAVA_17 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||
setSettings.java_globals.JAVA_8 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_17?.path === '') {
|
||||
setSettings.java_globals.JAVA_17 = undefined
|
||||
}
|
||||
|
||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_args = setSettings.envArgs
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
if (setSettings.java_globals.JAVA_8?.path) {
|
||||
setSettings.java_globals.JAVA_8.path = setSettings.java_globals.JAVA_8.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_17?.path) {
|
||||
setSettings.java_globals.JAVA_17.path = setSettings.java_globals.JAVA_17?.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
|
||||
if (!setSettings.hooks.pre_launch) {
|
||||
setSettings.hooks.pre_launch = null
|
||||
}
|
||||
if (!setSettings.hooks.wrapper) {
|
||||
setSettings.hooks.wrapper = null
|
||||
}
|
||||
if (!setSettings.hooks.post_exit) {
|
||||
setSettings.hooks.post_exit = null
|
||||
}
|
||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_args = setSettings.envArgs
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
|
||||
await set(setSettings)
|
||||
})
|
||||
if (!setSettings.hooks.pre_launch) {
|
||||
setSettings.hooks.pre_launch = null
|
||||
}
|
||||
if (!setSettings.hooks.wrapper) {
|
||||
setSettings.hooks.wrapper = null
|
||||
}
|
||||
if (!setSettings.hooks.post_exit) {
|
||||
setSettings.hooks.post_exit = null
|
||||
}
|
||||
|
||||
await set(setSettings)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -119,7 +136,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
id="max-downloads"
|
||||
v-model="settings.max_concurrent_downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:max="10"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
@@ -136,7 +153,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
id="max-writes"
|
||||
v-model="settings.max_concurrent_writes"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:max="50"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -126,22 +126,22 @@ import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { handleError, useBreadcrumbs, useLoading, useSearch } from '@/store/state'
|
||||
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
||||
import { showInFolder } from '@/helpers/utils.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const router = useRouter()
|
||||
const searchStore = useSearch()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const instance = ref(await get(route.params.id).catch(handleError))
|
||||
|
||||
searchStore.instanceContext = instance.value
|
||||
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
||||
breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
link: route.path,
|
||||
query: route.query,
|
||||
})
|
||||
|
||||
const loadingBar = useLoading()
|
||||
@@ -183,7 +183,6 @@ const stopInstance = async () => {
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
searchStore.instanceContext = instance.value
|
||||
}
|
||||
})
|
||||
|
||||
@@ -242,6 +241,7 @@ const handleOptionsClick = async (args) => {
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
path: `/browse/${instance.value.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: route.params.id },
|
||||
})
|
||||
break
|
||||
case 'edit':
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<Card class="log-card">
|
||||
<div class="button-row">
|
||||
<DropdownSelect
|
||||
v-model="selectedLogIndex"
|
||||
:default-value="0"
|
||||
name="Log date"
|
||||
:model-value="logs[selectedLogIndex]"
|
||||
:options="logs"
|
||||
:display-name="(option) => option?.name"
|
||||
:options="logs.map((_, index) => index)"
|
||||
:display-name="(option) => logs[option]?.name"
|
||||
:disabled="logs.length === 0"
|
||||
@change="(value) => (selectedLogIndex = value.index)"
|
||||
/>
|
||||
<div class="button-group">
|
||||
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
|
||||
@@ -30,7 +30,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div ref="logContainer" class="log-text">
|
||||
<span v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
<span
|
||||
v-for="(line, index) in logs[selectedLogIndex]?.stdout.split('\n')"
|
||||
:key="index"
|
||||
class="no-wrap"
|
||||
>
|
||||
{{ line }} <br />
|
||||
</span>
|
||||
</div>
|
||||
@@ -68,15 +72,18 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
async function getLiveLog() {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} else {
|
||||
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
||||
}
|
||||
if (route.params.id) {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} else {
|
||||
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
||||
}
|
||||
|
||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
async function getLogs() {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
@click="
|
||||
router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: $route.params.id },
|
||||
})
|
||||
"
|
||||
>
|
||||
@@ -80,7 +81,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
<div class="table-cell table-text name-cell">
|
||||
<router-link v-if="mod.slug" :to="`/project/${mod.slug}/`" class="mod-text">
|
||||
<router-link
|
||||
v-if="mod.slug"
|
||||
:to="{ path: `/project/${mod.slug}/`, query: { i: props.instance.path } }"
|
||||
class="mod-text"
|
||||
>
|
||||
<Avatar :src="mod.icon" />
|
||||
{{ mod.name }}
|
||||
</router-link>
|
||||
@@ -305,7 +310,6 @@ async function updateProject(mod) {
|
||||
|
||||
async function toggleDisableMod(mod) {
|
||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||
console.log(mod.disabled)
|
||||
mod.disabled = !mod.disabled
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<template>
|
||||
<ModalConfirm
|
||||
ref="modal_confirm"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
@proceed="removeProfile"
|
||||
/>
|
||||
<Modal ref="changeVersionsModal" header="Change instance versions">
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
@@ -105,8 +113,8 @@
|
||||
placeholder="Select categories..."
|
||||
@tag="
|
||||
(newTag) => {
|
||||
groups.push(newTag)
|
||||
availableGroups.push(newTag)
|
||||
groups.push(newTag.trim().substring(0, 32))
|
||||
availableGroups.push(newTag.trim().substring(0, 32))
|
||||
}
|
||||
"
|
||||
/>
|
||||
@@ -288,7 +296,7 @@
|
||||
id="delete-profile"
|
||||
class="btn btn-danger"
|
||||
:disabled="removing"
|
||||
@click="removeProfile"
|
||||
@click="$refs.modal_confirm.show()"
|
||||
>
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
@@ -311,6 +319,7 @@ import {
|
||||
XIcon,
|
||||
SaveIcon,
|
||||
HammerIcon,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -398,7 +407,8 @@ const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
|
||||
watch(
|
||||
[
|
||||
title,
|
||||
groups.value,
|
||||
groups,
|
||||
groups,
|
||||
overrideJavaInstall,
|
||||
javaInstall,
|
||||
overrideJavaArgs,
|
||||
@@ -406,17 +416,17 @@ watch(
|
||||
overrideEnvVars,
|
||||
envVars,
|
||||
overrideMemorySettings,
|
||||
memory.value,
|
||||
memory,
|
||||
overrideWindowSettings,
|
||||
resolution.value,
|
||||
resolution,
|
||||
overrideHooks,
|
||||
hooks.value,
|
||||
hooks,
|
||||
],
|
||||
async () => {
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
name: title.value,
|
||||
groups: groups.value,
|
||||
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
@@ -424,6 +434,10 @@ watch(
|
||||
if (overrideJavaInstall.value) {
|
||||
if (javaInstall.value.path !== '') {
|
||||
editProfile.java.override_version = javaInstall.value
|
||||
editProfile.java.override_version.path = editProfile.java.override_version.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -456,7 +470,8 @@ watch(
|
||||
}
|
||||
|
||||
await edit(props.instance.path, editProfile)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const repairing = ref(false)
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
<template>
|
||||
<div class="root-container">
|
||||
<div v-if="data" class="project-sidebar">
|
||||
<Instance v-if="instance" :instance="instance" small />
|
||||
<div v-if="instance" class="small-instance">
|
||||
<div class="instance">
|
||||
<Avatar
|
||||
:src="
|
||||
!instance.metadata.icon ||
|
||||
(instance.metadata.icon && instance.metadata.icon.startsWith('http'))
|
||||
? instance.metadata.icon
|
||||
: convertFileSrc(instance.metadata?.icon)
|
||||
"
|
||||
:alt="instance.metadata.name"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="small-instance_info">
|
||||
<span class="title">{{ instance.metadata.name }}</span>
|
||||
<span>
|
||||
{{
|
||||
instance.metadata.loader.charAt(0).toUpperCase() + instance.metadata.loader.slice(1)
|
||||
}}
|
||||
{{ instance.metadata.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
||||
<Avatar size="lg" :src="data.icon_url" />
|
||||
<div class="instance-info">
|
||||
@@ -119,8 +141,8 @@
|
||||
<span>Wiki</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="data.wiki_url"
|
||||
:href="data.wiki_url"
|
||||
v-if="data.discord_url"
|
||||
:href="data.discord_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
@@ -238,7 +260,12 @@ import {
|
||||
} from '@/assets/external'
|
||||
import { get_categories, get_loaders } from '@/helpers/tags'
|
||||
import { install as packInstall } from '@/helpers/pack'
|
||||
import { list, add_project_from_version as installMod, check_installed } from '@/helpers/profile'
|
||||
import {
|
||||
list,
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
get as getInstance,
|
||||
} from '@/helpers/profile'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -246,16 +273,13 @@ import { ref, shallowRef, watch } from 'vue'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
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'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
|
||||
const searchStore = useSearch()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
@@ -263,11 +287,11 @@ const breadcrumbs = useBreadcrumbs()
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const incompatibilityWarning = ref(null)
|
||||
|
||||
const options = ref(null)
|
||||
const instance = ref(searchStore.instanceContext)
|
||||
const installing = ref(false)
|
||||
|
||||
const [data, versions, members, dependencies, categories, loaders] = await Promise.all([
|
||||
const [data, versions, members, dependencies, categories, loaders, instance] = await Promise.all([
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project').then(shallowRef),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project').then(
|
||||
shallowRef
|
||||
@@ -280,6 +304,7 @@ const [data, versions, members, dependencies, categories, loaders] = await Promi
|
||||
),
|
||||
get_loaders().then(ref).catch(handleError),
|
||||
get_categories().then(ref).catch(handleError),
|
||||
route.query.i ? getInstance(route.query.i, true).then(ref) : Promise.resolve().then(ref),
|
||||
])
|
||||
|
||||
const installed = ref(
|
||||
@@ -587,4 +612,29 @@ const handleOptionsClick = (args) => {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.small-instance {
|
||||
background: var(--color-bg);
|
||||
padding: var(--gap-lg);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--gap-md);
|
||||
|
||||
.instance {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.small-instance_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user