Loading bar all the things

This commit is contained in:
CodexAdrian
2023-05-09 03:35:59 -04:00
parent 4aed2c14b9
commit faeeee23f4
9 changed files with 877 additions and 720 deletions

View File

@@ -1,13 +1,14 @@
<script setup>
import { onMounted, ref } from 'vue'
import { RouterView, RouterLink } from 'vue-router'
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, AnimatedLogo } from 'omorphia'
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon } from 'omorphia'
import { useTheming } from '@/store/state'
import AccountsCard from '@/components/ui/AccountsCard.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { get } from '@/helpers/settings'
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue'
const themeStore = useTheming()
@@ -28,7 +29,8 @@ defineExpose({
</script>
<template>
<div v-if="!loading" class="container">
<SplashScreen v-if="loading" app-loading />
<div v-else class="container">
<div class="nav-container" :class="{ expanded: !themeStore.collapsedNavigation }">
<div class="nav-section">
<suspense>
@@ -121,9 +123,6 @@ defineExpose({
</div>
</div>
</div>
<div v-else class="loading-page">
<AnimatedLogo class="initializing-icon" />
</div>
</template>
<style lang="scss" scoped>
@@ -347,23 +346,4 @@ defineExpose({
height: 100%;
gap: 1rem;
}
.loading-page {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #16181c;
height: 100vh;
.initializing-icon {
width: 12rem;
height: 12rem;
:deep(svg) {
width: 12rem;
height: 12rem;
}
}
}
</style>

View File

@@ -37,10 +37,10 @@
<Card v-if="showCard === true" ref="card" class="info-card">
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
<h3 class="info-title">
{{ loadingBar.bar_type.PackDownload.pack_name }}
{{ loadingBar.bar_type.pack_name ?? 'Installing Modpack' }}
</h3>
<ProgressBar :progress="Math.round(loadingBar.current)" />
<div class="row">{{ loadingBar.message }}</div>
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
</div>
</Card>
</transition>
@@ -67,7 +67,8 @@ const showCard = ref(false)
const currentProcesses = ref(await getRunningProfiles())
await process_listener(async () => {
await process_listener(async (event) => {
console.log(event)
await refresh()
})
@@ -89,11 +90,7 @@ const goToTerminal = () => {
router.push(`/instance/${encodeURIComponent(currentProcesses.value[0].path)}/logs`)
}
const currentLoadingBars = ref(
Object.values(await progress_bars_list()).filter(
(bar) => bar.bar_type !== 'StateInit' && Object.keys(bar.bar_type)[0] === 'PackDownload'
)
)
const currentLoadingBars = ref(Object.values(await progress_bars_list()))
await loading_listener(async (event) => {
console.log('loading listener', event.message, event)
@@ -102,9 +99,7 @@ await loading_listener(async (event) => {
const refreshInfo = async () => {
const currentLoadingBarCount = currentLoadingBars.value.length
currentLoadingBars.value = Object.values(await progress_bars_list()).filter(
(bar) => bar.bar_type !== 'StateInit' && Object.keys(bar.bar_type)[0] === 'PackDownload'
)
currentLoadingBars.value = Object.values(await progress_bars_list())
if (currentLoadingBars.value.length === 0) {
showCard.value = false
} else if (currentLoadingBarCount < currentLoadingBars.value.length) {

View File

@@ -0,0 +1,49 @@
<template>
<div class="page-loading" :class="{ 'app-loading': appLoading }">
<AnimatedLogo class="initializing-icon" />
</div>
</template>
<script setup>
import { AnimatedLogo } from 'omorphia'
defineProps({
appLoading: Boolean,
})
</script>
<style scoped lang="scss">
.page-loading {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
width: 100%;
&.app-loading {
background-color: #16181c;
height: 100vh;
}
.splash-text {
margin-top: 2rem;
font-size: 1rem;
font-weight: 500;
text-align: center;
width: 15rem;
}
}
.initializing-icon {
width: 12rem;
height: 12rem;
:deep(svg),
svg {
width: 12rem;
height: 12rem;
}
}
</style>

View File

@@ -13,7 +13,6 @@ import {
Card,
ClientIcon,
ServerIcon,
AnimatedLogo,
NavRow,
formatCategoryHeader,
} from 'omorphia'
@@ -23,6 +22,7 @@ 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 SplashScreen from '@/components/ui/SplashScreen.vue'
const route = useRoute()
@@ -36,25 +36,28 @@ const breadcrumbs = useBreadcrumbs()
const showSnapshots = ref(false)
const loading = ref(true)
const [categories, loaders, availableGameVersions] = await Promise.all([
get_categories(),
get_loaders(),
get_game_versions(),
])
const categories = ref([])
const loaders = ref([])
const availableGameVersions = ref([])
onMounted(() => {
onMounted(async () => {
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
get_categories(),
get_loaders(),
get_game_versions(),
])
breadcrumbs.setContext({ name: 'Browse', link: route.path })
if (searchStore.projectType === 'modpack') {
searchStore.instanceContext = null
}
searchStore.searchInput = ''
handleReset()
switchPage(1)
await handleReset(true)
loading.value = false
})
const sortedCategories = computed(() => {
const values = new Map()
for (const category of categories.filter(
for (const category of categories.value.filter(
(cat) =>
cat.project_type ===
(searchStore.projectType === 'datapack' ? 'mod' : searchStore.projectType)
@@ -67,7 +70,7 @@ const sortedCategories = computed(() => {
return values
})
const getSearchResults = async (shouldLoad = false) => {
const getSearchResults = async () => {
const queryString = searchStore.getQueryString()
if (searchStore.instanceContext) {
showVersions.value = false
@@ -75,16 +78,10 @@ const getSearchResults = async (shouldLoad = false) => {
searchStore.projectType === 'mod' || searchStore.projectType === 'resourcepack'
)
}
if (shouldLoad === true) {
loading.value = true
}
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
loading.value = false
searchStore.setSearchResults(response)
}
getSearchResults(true)
const handleReset = async () => {
searchStore.currentPage = 1
searchStore.offset = 0
@@ -131,221 +128,227 @@ watch(
</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)}`"
class="filter-checkbox"
@toggle="toggleFacet"
/>
</div>
</div>
<div
v-if="
showLoaders &&
searchStore.projectType !== 'datapack' &&
searchStore.projectType !== 'resourcepack' &&
searchStore.projectType !== 'shader'
"
class="loaders"
>
<h2>Loaders</h2>
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else 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="loader in loaders.filter((l) =>
l.supported_project_types?.includes(searchStore.projectType)
)"
:key="loader"
v-for="categoryList in Array.from(sortedCategories)"
:key="categoryList[0]"
class="categories"
>
<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)
"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-search-on-select="false"
:show-labels="false"
placeholder="Choose versions..."
@update:model-value="getSearchResults"
/>
</div>
<div class="open-source">
<h2>Open source</h2>
<Checkbox
v-model="searchStore.openSource"
class="filter-checkbox"
@click="getSearchResults"
>
Open source
</Checkbox>
</div>
</aside>
<div class="search">
<Card class="project-type-container">
<NavRow
:links="
searchStore.instanceContext
? [
{ label: 'Mods', href: `/browse/mod` },
{ label: 'Datapacks', href: `/browse/datapack` },
{ label: 'Shaders', href: `/browse/shader` },
{ label: 'Resource Packs', href: `/browse/resourcepack` },
]
: [
{ label: 'Modpacks', href: '/browse/modpack' },
{ label: 'Mods', href: '/browse/mod' },
{ label: 'Datapacks', href: '/browse/datapack' },
{ label: 'Shaders', href: '/browse/shader' },
{ label: 'Resource Packs', href: '/browse/resourcepack' },
]
"
/>
</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"
<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)}`"
class="filter-checkbox"
@toggle="toggleFacet"
/>
</div>
<span>Sort by</span>
<DropdownSelect
v-model="searchStore.filter"
name="Sort dropdown"
:options="[
'Relevance',
'Download count',
'Follow count',
'Recently published',
'Recently updated',
]"
class="sort-dropdown"
@change="getSearchResults"
/>
<span>Show per page</span>
<DropdownSelect
v-model="searchStore.limit"
name="Limit dropdown"
:options="[5, 10, 15, 20, 50, 100]"
:default-value="searchStore.limit.toString()"
:model-value="searchStore.limit.toString()"
class="limit-dropdown"
@change="getSearchResults"
</div>
<div
v-if="
showLoaders &&
searchStore.projectType !== 'datapack' &&
searchStore.projectType !== 'resourcepack' &&
searchStore.projectType !== 'shader'
"
class="loaders"
>
<h2>Loaders</h2>
<div
v-for="loader in loaders.filter((l) =>
l.supported_project_types?.includes(searchStore.projectType)
)"
: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)
"
: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>
</Card>
<Pagination
:page="searchStore.currentPage"
:count="searchStore.pageCount"
@switch-page="switchPage"
/>
<AnimatedLogo v-if="loading" class="loading" />
<section v-else class="project-list display-mode--list instance-results" role="list">
<ProjectCard
v-for="result in searchStore.searchResults"
:id="`${result?.project_id}/`"
:key="result?.project_id"
class="result-project-item"
:type="result?.project_type"
:name="result?.title"
:description="result?.description"
:icon-url="result?.icon_url"
:downloads="result?.downloads?.toString()"
:follows="result?.follows?.toString()"
:created-at="result?.date_created"
:updated-at="result?.date_modified"
:categories="[
...categories.filter(
(cat) =>
result?.display_categories.includes(cat.name) &&
cat.project_type === searchStore.projectType
),
...loaders.filter(
(loader) =>
result?.display_categories.includes(loader.name) &&
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"
<div class="open-source">
<h2>Open source</h2>
<Checkbox
v-model="searchStore.openSource"
class="filter-checkbox"
@click="getSearchResults"
>
Open source
</Checkbox>
</div>
</aside>
<div class="search">
<Card class="project-type-container">
<NavRow
:links="
searchStore.instanceContext
? [
{ label: 'Mods', href: `/browse/mod` },
{ label: 'Datapacks', href: `/browse/datapack` },
{ label: 'Shaders', href: `/browse/shader` },
{ label: 'Resource Packs', href: `/browse/resourcepack` },
]
: [
{ label: 'Modpacks', href: '/browse/modpack' },
{ label: 'Mods', href: '/browse/mod' },
{ label: 'Datapacks', href: '/browse/datapack' },
{ label: 'Shaders', href: '/browse/shader' },
{ label: 'Resource Packs', href: '/browse/resourcepack' },
]
"
/>
</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>
<span>Sort by</span>
<DropdownSelect
v-model="searchStore.filter"
name="Sort dropdown"
:options="[
'Relevance',
'Download count',
'Follow count',
'Recently published',
'Recently updated',
]"
class="sort-dropdown"
@change="getSearchResults"
/>
<span>Show per page</span>
<DropdownSelect
v-model="searchStore.limit"
name="Limit dropdown"
:options="[5, 10, 15, 20, 50, 100]"
:default-value="searchStore.limit.toString()"
:model-value="searchStore.limit.toString()"
class="limit-dropdown"
@change="getSearchResults"
/>
</div>
</Card>
<Pagination
:page="searchStore.currentPage"
:count="searchStore.pageCount"
@switch-page="switchPage"
/>
</section>
<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 === searchStore.projectType
),
...loaders.filter(
(loader) =>
result?.display_categories.includes(loader.name) &&
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"
/>
</section>
</div>
</div>
</div>
</transition>
</template>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
@@ -457,4 +460,14 @@ watch(
}
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,25 +1,34 @@
<script setup>
import RowDisplay from '@/components/RowDisplay.vue'
import { shallowRef } from 'vue'
import { onMounted, ref } from 'vue'
import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import SplashScreen from '@/components/ui/SplashScreen.vue'
const route = useRoute()
const breadcrumbs = useBreadcrumbs()
const profiles = await list()
const recentInstances = shallowRef(Object.values(profiles))
const loading = ref(true)
const recentInstances = ref(null)
onMounted(async () => {
recentInstances.value = Object.values(await list())
loading.value = false
})
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
</script>
<template>
<div class="page-container">
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
<RowDisplay label="Popular packs" :instances="recentInstances" :can-paginate="true" />
<RowDisplay label="Test" :instances="recentInstances" :can-paginate="true" />
</div>
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else class="page-container">
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
<RowDisplay label="Popular packs" :instances="recentInstances" :can-paginate="true" />
<RowDisplay label="Test" :instances="recentInstances" :can-paginate="true" />
</div>
</transition>
</template>
<style lang="scss" scoped>
@@ -30,4 +39,14 @@ breadcrumbs.setRootContext({ name: 'Home', link: route.path })
justify-content: center;
width: 100%;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,19 +1,28 @@
<script setup>
import GridDisplay from '@/components/GridDisplay.vue'
import { shallowRef } from 'vue'
import { onMounted, ref } from 'vue'
import { list } from '@/helpers/profile'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { loading_listener } from '@/helpers/events.js'
import { progress_bars_list } from '@/helpers/state.js'
import SplashScreen from '@/components/ui/SplashScreen.vue'
const route = useRoute()
const breadcrumbs = useBreadcrumbs()
const instances = shallowRef(Object.values(await list()))
const loadingInstances = shallowRef(Object.values(await progress_bars_list()))
const loading = ref(true)
const instances = ref(null)
const loadingInstances = ref(null)
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
onMounted(async () => {
instances.value = Object.values(await list())
loadingInstances.value = Object.values(await progress_bars_list())
loading.value = false
})
loading_listener(async (profile) => {
console.log(profile)
instances.value = Object.values(await list())
@@ -22,8 +31,23 @@ loading_listener(async (profile) => {
</script>
<template>
<div>
<GridDisplay label="Instances" :instances="instances" />
<GridDisplay label="Modpacks" :instances="instances" />
</div>
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else>
<GridDisplay label="Instances" :instances="instances" />
<GridDisplay label="Modpacks" :instances="instances" />
</div>
</transition>
</template>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,5 +1,5 @@
<script setup>
import { ref, watch } from 'vue'
import { onMounted, ref, watch } from 'vue'
import {
Card,
Slider,
@@ -19,17 +19,24 @@ import { useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings'
import { find_jre_8_jres, find_jre_17_jres, get_jre } from '@/helpers/jre'
import { open } from '@tauri-apps/api/dialog'
import SplashScreen from '@/components/ui/SplashScreen.vue'
const themeStore = useTheming()
const fetchSettings = await get()
const settings = ref(null)
const loading = ref(true)
if (!fetchSettings.java_globals?.JAVA_8)
fetchSettings.java_globals.JAVA_8 = { path: '', version: '' }
if (!fetchSettings.java_globals?.JAVA_17)
fetchSettings.java_globals.JAVA_17 = { path: '', version: '' }
onMounted(async () => {
const fetchSettings = await get()
const settings = ref(fetchSettings)
if (!fetchSettings.java_globals?.JAVA_8)
fetchSettings.java_globals.JAVA_8 = { path: '', version: '' }
if (!fetchSettings.java_globals?.JAVA_17)
fetchSettings.java_globals.JAVA_17 = { path: '', version: '' }
settings.value = fetchSettings
loading.value = false
})
const chosenInstallOptions = ref([])
const browsingInstall = ref(0)
@@ -119,232 +126,238 @@ const setJavaInstall = (javaInstall) => {
</script>
<template>
<div>
<Modal ref="detectJavaModal" header="Select java version">
<div class="auto-detect-modal">
<div class="table">
<div class="table-row table-head">
<div class="table-cell table-text">Version</div>
<div class="table-cell table-text">Path</div>
<div class="table-cell table-text">Actions</div>
</div>
<div
v-for="javaInstall in chosenInstallOptions"
:key="javaInstall.path"
class="table-row"
>
<div class="table-cell table-text">
<span>{{ javaInstall.version }}</span>
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else>
<Modal ref="detectJavaModal" header="Select java version">
<div class="auto-detect-modal">
<div class="table">
<div class="table-row table-head">
<div class="table-cell table-text">Version</div>
<div class="table-cell table-text">Path</div>
<div class="table-cell table-text">Actions</div>
</div>
<div class="table-cell table-text">
<span>{{ javaInstall.path }}</span>
</div>
<div class="table-cell table-text manage">
<Button
:disabled="
settings.java_globals.JAVA_17.path === javaInstall.path ||
settings.java_globals.JAVA_8.path === javaInstall.path
"
class="select-btn"
@click="() => setJavaInstall(javaInstall)"
>
<span
v-if="
<div
v-for="javaInstall in chosenInstallOptions"
:key="javaInstall.path"
class="table-row"
>
<div class="table-cell table-text">
<span>{{ javaInstall.version }}</span>
</div>
<div class="table-cell table-text">
<span>{{ javaInstall.path }}</span>
</div>
<div class="table-cell table-text manage">
<Button
:disabled="
settings.java_globals.JAVA_17.path === javaInstall.path ||
settings.java_globals.JAVA_8.path === javaInstall.path
"
class="select-btn"
@click="() => setJavaInstall(javaInstall)"
>
<CheckIcon />Selected
</span>
<span v-else><PlusIcon />Select</span>
</Button>
<span
v-if="
settings.java_globals.JAVA_17.path === javaInstall.path ||
settings.java_globals.JAVA_8.path === javaInstall.path
"
>
<CheckIcon />Selected
</span>
<span v-else><PlusIcon />Select</span>
</Button>
</div>
</div>
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
<div class="table-cell table-text">No JARS Found!</div>
</div>
</div>
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
<div class="table-cell table-text">No JARS Found!</div>
</div>
</Modal>
<Card class="theming">
<h2>Display</h2>
<div class="toggle-setting">
<div class="description">
<h3>Color theme</h3>
<p>Change the global launcher color theme.</p>
</div>
<DropdownSelect
name="Theme dropdown"
:options="themeStore.themeOptions"
:default-value="settings.theme"
:model-value="settings.theme"
class="theme-dropdown"
@change="handleTheme"
/>
</div>
<div class="toggle-setting">
<div class="description">
<h3>Collapsed navigation mode</h3>
<p>Change the style of the side navigation bar</p>
</div>
<Toggle
:model-value="themeStore.collapsedNavigation"
:checked="themeStore.collapsedNavigation"
@update:model-value="(value) => handleCollapse(value)"
/>
</div>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Java</h2>
<div class="settings-group">
<h3>Java 17 Location</h3>
<div class="toggle-setting">
<input
v-model="settings.java_globals.JAVA_17.path"
type="text"
class="input installation-input"
placeholder="/path/to/java17"
/>
<span class="installation-buttons">
<Button @click="() => loadJavaModal(17)">
<SearchIcon />
Auto Detect
</Button>
<Button @click="handleJava17FileInput">
<BrowseIcon />
Browse
</Button>
<Button @click="handleJava17Test">
<PlayIcon />
Test
</Button>
<AnimatedLogo v-if="testingJava17 === true" class="testing-loader" />
<CheckIcon
v-else-if="java17Success === true && testingJava17 === false"
class="test-success"
/>
<XIcon
v-else-if="java17Success === false && testingJava17 === false"
class="test-fail"
/>
</span>
</div>
</div>
</div>
</Modal>
<Card class="theming">
<h2>Display</h2>
<div class="toggle-setting">
<div class="description">
<h3>Color theme</h3>
<p>Change the global launcher color theme.</p>
<div class="settings-group">
<h3>Java 8 Location</h3>
<div class="toggle-setting">
<input
v-model="settings.java_globals.JAVA_8.path"
type="text"
class="input installation-input"
placeholder="/path/to/java8"
/>
<span class="installation-buttons">
<Button @click="() => loadJavaModal(8)">
<SearchIcon />
Auto Detect
</Button>
<Button @click="handleJava8FileInput">
<BrowseIcon />
Browse
</Button>
<Button @click="handleJava8Test">
<PlayIcon />
Test
</Button>
<AnimatedLogo v-if="testingJava8 === true" class="testing-loader" />
<CheckIcon
v-else-if="java8Success === true && testingJava8 === false"
class="test-success"
/>
<XIcon
v-else-if="java8Success === false && testingJava8 === false"
class="test-fail"
/>
</span>
</div>
</div>
<DropdownSelect
name="Theme dropdown"
:options="themeStore.themeOptions"
:default-value="settings.theme"
:model-value="settings.theme"
class="theme-dropdown"
@change="handleTheme"
/>
</div>
<div class="toggle-setting">
<div class="description">
<h3>Collapsed navigation mode</h3>
<p>Change the style of the side navigation bar</p>
</div>
<Toggle
:model-value="themeStore.collapsedNavigation"
:checked="themeStore.collapsedNavigation"
@update:model-value="(value) => handleCollapse(value)"
/>
</div>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Java</h2>
<div class="settings-group">
<h3>Java 17 Location</h3>
<div class="toggle-setting">
<hr class="card-divider" />
<div class="settings-group">
<h3>Java Arguments</h3>
<input
v-model="settings.java_globals.JAVA_17.path"
v-model="settings.custom_java_args"
type="text"
class="input installation-input"
placeholder="/path/to/java17"
placeholder="Enter java arguments..."
/>
<span class="installation-buttons">
<Button @click="() => loadJavaModal(17)">
<SearchIcon />
Auto Detect
</Button>
<Button @click="handleJava17FileInput">
<BrowseIcon />
Browse
</Button>
<Button @click="handleJava17Test">
<PlayIcon />
Test
</Button>
<AnimatedLogo v-if="testingJava17 === true" class="testing-loader" />
<CheckIcon
v-else-if="java17Success === true && testingJava17 === false"
class="test-success"
/>
<XIcon
v-else-if="java17Success === false && testingJava17 === false"
class="test-fail"
/>
</span>
</div>
</div>
<div class="settings-group">
<h3>Java 8 Location</h3>
<div class="toggle-setting">
<div class="settings-group">
<h3>Environment Arguments</h3>
<input
v-model="settings.java_globals.JAVA_8.path"
v-model="settings.custom_env_args"
type="text"
class="input installation-input"
placeholder="/path/to/java8"
placeholder="Enter environment arguments..."
/>
<span class="installation-buttons">
<Button @click="() => loadJavaModal(8)">
<SearchIcon />
Auto Detect
</Button>
<Button @click="handleJava8FileInput">
<BrowseIcon />
Browse
</Button>
<Button @click="handleJava8Test">
<PlayIcon />
Test
</Button>
<AnimatedLogo v-if="testingJava8 === true" class="testing-loader" />
<CheckIcon
v-else-if="java8Success === true && testingJava8 === false"
class="test-success"
/>
<XIcon v-else-if="java8Success === false && testingJava8 === false" class="test-fail" />
</span>
</div>
</div>
<hr class="card-divider" />
<div class="settings-group">
<h3>Java Arguments</h3>
<input
v-model="settings.custom_java_args"
type="text"
class="input installation-input"
placeholder="Enter java arguments..."
/>
</div>
<div class="settings-group">
<h3>Environment Arguments</h3>
<input
v-model="settings.custom_env_args"
type="text"
class="input installation-input"
placeholder="Enter environment arguments..."
/>
</div>
<hr class="card-divider" />
<div class="settings-group">
<div class="sliders">
<span class="slider">
Minimum Memory
<Slider v-model="settings.memory.minimum" :min="1000" :max="8200" :step="10" />
</span>
<span class="slider">
Maximum Memory
<Slider v-model="settings.memory.maximum" :min="1000" :max="8200" :step="10" />
</span>
</div>
</div>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Window Size</h2>
<div class="settings-group">
<hr class="card-divider" />
<div class="settings-group">
<div class="sliders">
<span class="slider">
Width
<Slider v-model="settings.game_resolution[0]" :min="400" :max="2562" :step="2" />
Minimum Memory
<Slider v-model="settings.memory.minimum" :min="1000" :max="8200" :step="10" />
</span>
<span class="slider">
Height
<Slider v-model="settings.game_resolution[1]" :min="400" :max="2562" :step="2" />
Maximum Memory
<Slider v-model="settings.memory.maximum" :min="1000" :max="8200" :step="10" />
</span>
</div>
</div>
</div>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Launcher Settings</h2>
<div class="settings-group">
<h3>Resource Management</h3>
<div class="toggle-setting">
<span>Maximum Concurrent Downloads</span>
<Slider
v-model="settings.max_concurrent_downloads"
class="concurrent-downloads"
:min="1"
:max="100"
:step="1"
/>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Window Size</h2>
<div class="settings-group">
<div class="settings-group">
<div class="sliders">
<span class="slider">
Width
<Slider v-model="settings.game_resolution[0]" :min="400" :max="2562" :step="2" />
</span>
<span class="slider">
Height
<Slider v-model="settings.game_resolution[1]" :min="400" :max="2562" :step="2" />
</span>
</div>
</div>
</div>
</div>
</Card>
<Card class="settings-card">
<h2 class="settings-title">Commands</h2>
<div class="settings-group">
<div class="toggle-setting">
Pre Launch
<input v-model="settings.hooks.pre_launch" type="text" class="input" />
</Card>
<Card class="settings-card">
<h2 class="settings-title">Launcher Settings</h2>
<div class="settings-group">
<h3>Resource Management</h3>
<div class="toggle-setting">
<span>Maximum Concurrent Downloads</span>
<Slider
v-model="settings.max_concurrent_downloads"
class="concurrent-downloads"
:min="1"
:max="100"
:step="1"
/>
</div>
</div>
<div class="toggle-setting">
Wrapper
<input v-model="settings.hooks.wrapper" type="text" class="input" />
</Card>
<Card class="settings-card">
<h2 class="settings-title">Commands</h2>
<div class="settings-group">
<div class="toggle-setting">
Pre Launch
<input v-model="settings.hooks.pre_launch" type="text" class="input" />
</div>
<div class="toggle-setting">
Wrapper
<input v-model="settings.hooks.wrapper" type="text" class="input" />
</div>
<div class="toggle-setting">
Post Launch
<input v-model="settings.hooks.post_exit" type="text" class="input" />
</div>
</div>
<div class="toggle-setting">
Post Launch
<input v-model="settings.hooks.post_exit" type="text" class="input" />
</div>
</div>
</Card>
</div>
</Card>
</div>
</transition>
</template>
<style lang="scss" scoped>
@@ -467,4 +480,14 @@ const setJavaInstall = (javaInstall) => {
.test-fail {
color: var(--color-red);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,50 +1,53 @@
<template>
<div class="instance-container">
<div class="side-cards">
<Card class="instance-card">
<Avatar size="lg" :src="convertFileSrc(instance.metadata.icon)" />
<div class="instance-info">
<h2 class="name">{{ instance.metadata.name }}</h2>
<span class="metadata">
{{ instance.metadata.loader }} {{ instance.metadata.game_version }}
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else class="instance-container">
<div class="side-cards">
<Card class="instance-card">
<Avatar size="lg" :src="convertFileSrc(instance.metadata.icon)" />
<div class="instance-info">
<h2 class="name">{{ instance.metadata.name }}</h2>
<span class="metadata">
{{ instance.metadata.loader }} {{ instance.metadata.game_version }}
</span>
</div>
<span class="button-group">
<Button
:color="instance.installed ? 'primary' : ''"
class="instance-button"
:disabled="!instance.installed"
@click="run($route.params.id)"
>
<PlayIcon v-if="instance.installed" />
<AnimatedLogo v-else class="loading-icon" />
{{ instance.installed ? 'Play' : 'Installing' }}
</Button>
<Button class="instance-button" icon-only>
<OpenFolderIcon />
</Button>
</span>
</Card>
<div class="pages-list">
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/`" class="btn">
<BoxIcon />
Mods
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/options`" class="btn">
<SettingsIcon />
Options
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/logs`" class="btn">
<FileIcon />
Logs
</RouterLink>
</div>
<span class="button-group">
<Button
:color="instance.installed ? 'primary' : ''"
class="instance-button"
:disabled="!instance.installed"
@click="run($route.params.id)"
>
<PlayIcon v-if="instance.installed" />
<AnimatedLogo v-else class="loading-icon" />
{{ instance.installed ? 'Play' : 'Installing' }}
</Button>
<Button class="instance-button" icon-only>
<OpenFolderIcon />
</Button>
</span>
</Card>
<div class="pages-list">
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/`" class="btn">
<BoxIcon />
Mods
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/options`" class="btn">
<SettingsIcon />
Options
</RouterLink>
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/logs`" class="btn">
<FileIcon />
Logs
</RouterLink>
</div>
<div class="content">
<Promotion />
<router-view :instance="instance" />
</div>
</div>
<div class="content">
<Promotion />
<router-view :instance="instance" />
</div>
</div>
</transition>
</template>
<script setup>
import {
@@ -60,23 +63,29 @@ import {
import { PlayIcon, OpenFolderIcon } from '@/assets/icons'
import { get, run } from '@/helpers/profile'
import { useRoute } from 'vue-router'
import { shallowRef } from 'vue'
import { onMounted, ref } from 'vue'
import { convertFileSrc } from '@tauri-apps/api/tauri'
import { useSearch } from '@/store/search'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { profile_listener } from '@/helpers/events.js'
import SplashScreen from '@/components/ui/SplashScreen.vue'
const route = useRoute()
const searchStore = useSearch()
const breadcrumbs = useBreadcrumbs()
const instance = shallowRef(await get(route.params.id))
searchStore.instanceContext = instance.value
const instance = ref(null)
const loading = ref(true)
breadcrumbs.setName('Instance', instance.value.metadata.name)
breadcrumbs.setContext({
name: instance.value.metadata.name,
link: route.path,
onMounted(async () => {
instance.value = await get(route.params.id)
searchStore.instanceContext = instance.value
breadcrumbs.setName('Instance', instance.value.metadata.name)
breadcrumbs.setContext({
name: instance.value.metadata.name,
link: route.path,
})
loading.value = false
})
profile_listener(async (event) => {
@@ -319,4 +328,14 @@ Button {
fill: var(--color-base) !important;
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,190 +1,201 @@
<template>
<div class="root-container">
<div v-if="data" class="project-sidebar">
<Instance v-if="instance" :instance="instance" small />
<Card class="sidebar-card">
<Avatar size="lg" :src="data.icon_url" />
<div class="instance-info">
<h2 class="name">{{ data.title }}</h2>
{{ data.description }}
</div>
<Categories
class="tags"
type=""
:categories="[
...categories.filter(
(cat) => data.categories.includes(cat.name) && cat.project_type === 'mod'
),
...loaders.filter(
(loader) =>
data.categories.includes(loader.name) &&
loader.supported_project_types?.includes('modpack')
),
]"
>
<EnvironmentIndicator
:client-side="data.client_side"
:server-side="data.server_side"
:type="data.project_type"
<transition name="fade">
<SplashScreen v-if="loading" />
<div v-else class="root-container">
<div v-if="data" class="project-sidebar">
<Instance v-if="instance" :instance="instance" small />
<Card class="sidebar-card">
<Avatar size="lg" :src="data.icon_url" />
<div class="instance-info">
<h2 class="name">{{ data.title }}</h2>
{{ data.description }}
</div>
<Categories
class="tags"
type=""
:categories="[
...categories.filter(
(cat) => data.categories.includes(cat.name) && cat.project_type === 'mod'
),
...loaders.filter(
(loader) =>
data.categories.includes(loader.name) &&
loader.supported_project_types?.includes('modpack')
),
]"
>
<EnvironmentIndicator
:client-side="data.client_side"
:server-side="data.server_side"
:type="data.project_type"
/>
</Categories>
<hr class="card-divider" />
<div class="button-group">
<Button
color="primary"
class="instance-button"
:disabled="installed === true || installing === true"
@click="install(null)"
>
<DownloadIcon v-if="!installed && !installing" />
<CheckIcon v-else-if="installed" />
{{ installing ? 'Installing...' : installed ? 'Installed' : 'Install' }}
</Button>
<a
:href="`https://modrinth.com/${data.project_type}/${data.slug}`"
rel="external"
target="_blank"
class="btn"
>
<ExternalIcon />
Site
</a>
</div>
<hr class="card-divider" />
<div class="stats">
<div class="stat">
<DownloadIcon aria-hidden="true" />
<p>
<strong>{{ formatNumber(data.downloads) }}</strong>
<span class="stat-label">
download<span v-if="data.downloads !== '1'">s</span></span
>
</p>
</div>
<div class="stat">
<HeartIcon aria-hidden="true" />
<p>
<strong>{{ formatNumber(data.followers) }}</strong>
<span class="stat-label">
follower<span v-if="data.followers !== '1'">s</span></span
>
</p>
</div>
<div class="stat date">
<CalendarIcon aria-hidden="true" />
<span
><span class="date-label">Created </span>
{{ dayjs(data.published).fromNow() }}</span
>
</div>
<div class="stat date">
<UpdatedIcon aria-hidden="true" />
<span
><span class="date-label">Updated </span> {{ dayjs(data.updated).fromNow() }}</span
>
</div>
</div>
<hr class="card-divider" />
<div class="button-group">
<Button class="instance-button">
<ReportIcon />
Report
</Button>
<Button class="instance-button">
<HeartIcon />
Follow
</Button>
</div>
<hr class="card-divider" />
<div class="links">
<a
v-if="data.issues_url"
:href="data.issues_url"
class="title"
rel="noopener nofollow ugc"
>
<IssuesIcon aria-hidden="true" />
<span>Issues</span>
</a>
<a
v-if="data.source_url"
:href="data.source_url"
class="title"
rel="noopener nofollow ugc"
>
<CodeIcon aria-hidden="true" />
<span>Source</span>
</a>
<a v-if="data.wiki_url" :href="data.wiki_url" class="title" rel="noopener nofollow ugc">
<WikiIcon aria-hidden="true" />
<span>Wiki</span>
</a>
<a v-if="data.wiki_url" :href="data.wiki_url" class="title" rel="noopener nofollow ugc">
<DiscordIcon aria-hidden="true" />
<span>Discord</span>
</a>
<a
v-for="(donation, index) in data.donation_urls"
:key="index"
:href="donation.url"
rel="noopener nofollow ugc"
>
<BuyMeACoffeeIcon v-if="donation.id === 'bmac'" aria-hidden="true" />
<PatreonIcon v-else-if="donation.id === 'patreon'" aria-hidden="true" />
<KoFiIcon v-else-if="donation.id === 'ko-fi'" aria-hidden="true" />
<PaypalIcon v-else-if="donation.id === 'paypal'" aria-hidden="true" />
<OpenCollectiveIcon
v-else-if="donation.id === 'open-collective'"
aria-hidden="true"
/>
<HeartIcon v-else-if="donation.id === 'github'" />
<CoinsIcon v-else />
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
<span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'">GitHub Sponsors</span>
<span v-else>Donate</span>
</a>
</div>
</Card>
</div>
<div v-if="data" class="content-container">
<Promotion />
<Card class="tabs">
<NavRow
v-if="data.gallery.length > 0"
:links="[
{
label: 'Description',
href: `/project/${$route.params.id}/`,
},
{
label: 'Versions',
href: `/project/${$route.params.id}/versions`,
},
{
label: 'Gallery',
href: `/project/${$route.params.id}/gallery`,
},
]"
/>
</Categories>
<hr class="card-divider" />
<div class="button-group">
<Button
color="primary"
class="instance-button"
:disabled="installed === true || installing === true"
@click="install(null)"
>
<DownloadIcon v-if="!installed && !installing" />
<CheckIcon v-else-if="installed" />
{{ installing ? 'Installing...' : installed ? 'Installed' : 'Install' }}
</Button>
<a
:href="`https://modrinth.com/${data.project_type}/${data.slug}`"
rel="external"
target="_blank"
class="btn"
>
<ExternalIcon />
Site
</a>
</div>
<hr class="card-divider" />
<div class="stats">
<div class="stat">
<DownloadIcon aria-hidden="true" />
<p>
<strong>{{ formatNumber(data.downloads) }}</strong>
<span class="stat-label"> download<span v-if="data.downloads !== '1'">s</span></span>
</p>
</div>
<div class="stat">
<HeartIcon aria-hidden="true" />
<p>
<strong>{{ formatNumber(data.followers) }}</strong>
<span class="stat-label"> follower<span v-if="data.followers !== '1'">s</span></span>
</p>
</div>
<div class="stat date">
<CalendarIcon aria-hidden="true" />
<span
><span class="date-label">Created </span> {{ dayjs(data.published).fromNow() }}</span
>
</div>
<div class="stat date">
<UpdatedIcon aria-hidden="true" />
<span
><span class="date-label">Updated </span> {{ dayjs(data.updated).fromNow() }}</span
>
</div>
</div>
<hr class="card-divider" />
<div class="button-group">
<Button class="instance-button">
<ReportIcon />
Report
</Button>
<Button class="instance-button">
<HeartIcon />
Follow
</Button>
</div>
<hr class="card-divider" />
<div class="links">
<a
v-if="data.issues_url"
:href="data.issues_url"
class="title"
rel="noopener nofollow ugc"
>
<IssuesIcon aria-hidden="true" />
<span>Issues</span>
</a>
<a
v-if="data.source_url"
:href="data.source_url"
class="title"
rel="noopener nofollow ugc"
>
<CodeIcon aria-hidden="true" />
<span>Source</span>
</a>
<a v-if="data.wiki_url" :href="data.wiki_url" class="title" rel="noopener nofollow ugc">
<WikiIcon aria-hidden="true" />
<span>Wiki</span>
</a>
<a v-if="data.wiki_url" :href="data.wiki_url" class="title" rel="noopener nofollow ugc">
<DiscordIcon aria-hidden="true" />
<span>Discord</span>
</a>
<a
v-for="(donation, index) in data.donation_urls"
:key="index"
:href="donation.url"
rel="noopener nofollow ugc"
>
<BuyMeACoffeeIcon v-if="donation.id === 'bmac'" aria-hidden="true" />
<PatreonIcon v-else-if="donation.id === 'patreon'" aria-hidden="true" />
<KoFiIcon v-else-if="donation.id === 'ko-fi'" aria-hidden="true" />
<PaypalIcon v-else-if="donation.id === 'paypal'" aria-hidden="true" />
<OpenCollectiveIcon v-else-if="donation.id === 'open-collective'" aria-hidden="true" />
<HeartIcon v-else-if="donation.id === 'github'" />
<CoinsIcon v-else />
<span v-if="donation.id === 'bmac'">Buy Me a Coffee</span>
<span v-else-if="donation.id === 'patreon'">Patreon</span>
<span v-else-if="donation.id === 'paypal'">PayPal</span>
<span v-else-if="donation.id === 'ko-fi'">Ko-fi</span>
<span v-else-if="donation.id === 'github'">GitHub Sponsors</span>
<span v-else>Donate</span>
</a>
</div>
</Card>
</div>
<div v-if="data" class="content-container">
<Promotion />
<Card class="tabs">
<NavRow
v-if="data.gallery.length > 0"
:links="[
{
label: 'Description',
href: `/project/${$route.params.id}/`,
},
{
label: 'Versions',
href: `/project/${$route.params.id}/versions`,
},
{
label: 'Gallery',
href: `/project/${$route.params.id}/gallery`,
},
]"
<NavRow
v-else
:links="[
{
label: 'Description',
href: `/project/${$route.params.id}/`,
},
{
label: 'Versions',
href: `/project/${$route.params.id}/versions`,
},
]"
/>
</Card>
<RouterView
:project="data"
:versions="versions"
:members="members"
:dependencies="dependencies"
:install="install"
:installed="installed"
/>
<NavRow
v-else
:links="[
{
label: 'Description',
href: `/project/${$route.params.id}/`,
},
{
label: 'Versions',
href: `/project/${$route.params.id}/versions`,
},
]"
/>
</Card>
<RouterView
:project="data"
:versions="versions"
:members="members"
:dependencies="dependencies"
:install="install"
:installed="installed"
/>
</div>
</div>
</div>
</transition>
<InstallConfirmModal ref="confirmModal" />
<InstanceInstallModal ref="modInstallModal" />
</template>
@@ -226,37 +237,51 @@ import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime'
import { ofetch } from 'ofetch'
import { useRoute, useRouter } from 'vue-router'
import { ref, shallowRef, watch } from 'vue'
import { onMounted, ref, watch } from 'vue'
import { checkInstalled, 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 SplashScreen from '@/components/ui/SplashScreen.vue'
const searchStore = useSearch()
const route = useRoute()
const router = useRouter()
const breadcrumbs = useBreadcrumbs()
const loading = ref(true)
const confirmModal = ref(null)
const modInstallModal = ref(null)
const loaders = ref(await get_loaders())
const categories = ref(await get_categories())
const loaders = ref(null)
const categories = ref(null)
const instance = ref(searchStore.instanceContext)
const installing = ref(false)
const [data, versions, members, dependencies] = await Promise.all([
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef),
])
const data = ref(null)
const versions = ref(null)
const members = ref(null)
const dependencies = ref(null)
const installed = ref(false)
const installed = ref(instance.value && checkInstalled(instance.value, data.value.id))
onMounted(async () => {
;[data.value, versions.value, members.value, dependencies.value] = await Promise.all([
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}`),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`),
])
breadcrumbs.setName('Project', data.value.title)
loaders.value = await get_loaders()
categories.value = await get_categories()
loading.value = false
installed.value = instance.value && checkInstalled(instance.value, data.value.id)
breadcrumbs.setName('Project', data.value.title)
})
watch(
() => route.params.id,
@@ -291,7 +316,7 @@ async function install(version) {
.map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === data.value.id)
) {
let id = await packInstall(queuedVersionData.id)
let id = await packInstall(queuedVersionData.id, data.value.title)
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
} else {
confirmModal.value.show(queuedVersionData.id)
@@ -487,4 +512,14 @@ async function install(version) {
color: var(--color-contrast);
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>