You've already forked AstralRinth
forked from didirus/AstralRinth
Migrate to SQLite for Internal Launcher Data (#1300)
* initial migration * barebones profiles * Finish profiles * Add back file watcher * UI support progress * Finish most of cache * Fix options page * Fix forge, finish modrinth auth * Accounts, process cache * Run SQLX prepare * Finish * Run lint + actions * Fix version to be compat with windows * fix lint * actually fix lint * actually fix lint again
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
NavRow,
|
||||
Card,
|
||||
SearchFilter,
|
||||
Avatar,
|
||||
} from '@modrinth/ui'
|
||||
import { formatCategoryHeader, formatCategory } from '@modrinth/utils'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
@@ -17,29 +18,22 @@ import { handleError } from '@/store/state'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Avatar } from '@modrinth/ui'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { check_installed, get, get as getInstance } from '@/helpers/profile.js'
|
||||
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { isOffline } from '@/helpers/utils'
|
||||
import { offline_listener } from '@/helpers/events'
|
||||
|
||||
import { get_search_results } from '@/helpers/cache.js'
|
||||
import { debounce } from '@/helpers/utils.js'
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const offline = ref(await isOffline())
|
||||
const unlistenOffline = await offline_listener((b) => {
|
||||
offline.value = b
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
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 })
|
||||
@@ -65,6 +59,7 @@ const maxResults = ref(20)
|
||||
const currentPage = ref(1)
|
||||
const projectType = ref(route.params.projectType)
|
||||
const instanceContext = ref(null)
|
||||
const instanceProjects = ref(null)
|
||||
const ignoreInstanceLoaders = ref(false)
|
||||
const ignoreInstanceGameVersions = ref(false)
|
||||
|
||||
@@ -88,7 +83,10 @@ if (route.query.il) {
|
||||
ignoreInstanceLoaders.value = route.query.il === 'true'
|
||||
}
|
||||
if (route.query.i) {
|
||||
instanceContext.value = await getInstance(route.query.i, true)
|
||||
;[instanceContext.value, instanceProjects.value] = await Promise.all([
|
||||
getInstance(route.query.i).catch(handleError),
|
||||
getInstanceProjects(route.query.i).catch(handleError),
|
||||
])
|
||||
}
|
||||
if (route.query.q) {
|
||||
query.value = route.query.q
|
||||
@@ -144,18 +142,16 @@ if (route.query.ai) {
|
||||
}
|
||||
|
||||
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)}`]
|
||||
orFacets.value = [`categories:${encodeURIComponent(instanceContext.value.loader)}`]
|
||||
}
|
||||
if (!ignoreInstanceGameVersions.value) {
|
||||
selectedVersions.value = [instanceContext.value.metadata.game_version]
|
||||
selectedVersions.value = [instanceContext.value.game_version]
|
||||
}
|
||||
}
|
||||
if (
|
||||
@@ -224,13 +220,11 @@ async function refreshSearch() {
|
||||
}
|
||||
|
||||
if (hideAlreadyInstalled.value) {
|
||||
const installedMods = await get(instanceContext.value.path, false).then((x) =>
|
||||
Object.values(x.projects)
|
||||
.filter((x) => x.metadata.project)
|
||||
.map((x) => x.metadata.project.id),
|
||||
)
|
||||
const installedMods = Object.values(instanceProjects.value)
|
||||
.filter((x) => x.metadata)
|
||||
.map((x) => x.metadata.project_id)
|
||||
|
||||
installedMods.map((x) => [`project_id != ${x}`]).forEach((x) => formattedFacets.push(x))
|
||||
console.log(`facets=${JSON.stringify(formattedFacets)}`)
|
||||
}
|
||||
|
||||
params.push(`facets=${JSON.stringify(formattedFacets)}`)
|
||||
@@ -246,24 +240,24 @@ async function refreshSearch() {
|
||||
}
|
||||
}
|
||||
|
||||
let val = `${base}${url}`
|
||||
|
||||
let rawResults = await useFetch(val, 'search results', offline.value)
|
||||
let rawResults = await get_search_results(`?${url}`)
|
||||
if (!rawResults) {
|
||||
rawResults = {
|
||||
hits: [],
|
||||
total_hits: 0,
|
||||
limit: 1,
|
||||
result: {
|
||||
hits: [],
|
||||
total_hits: 0,
|
||||
limit: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (instanceContext.value) {
|
||||
for (val of rawResults.hits) {
|
||||
val.installed = await check_installed(instanceContext.value.path, val.project_id).then(
|
||||
(x) => (val.installed = x),
|
||||
for (const val of rawResults.result.hits) {
|
||||
val.installed = Object.values(instanceProjects.value).some(
|
||||
(x) => x.metadata && x.metadata.project_id === val.project_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
results.value = rawResults
|
||||
results.value = rawResults.result
|
||||
}
|
||||
|
||||
async function onSearchChange(newPageNumber) {
|
||||
@@ -282,6 +276,8 @@ async function onSearchChange(newPageNumber) {
|
||||
}
|
||||
}
|
||||
|
||||
const debouncedSearchChange = debounce(() => onSearchChange(1), 200)
|
||||
|
||||
const searchWrapper = ref(null)
|
||||
async function onSearchChangeToTop(newPageNumber) {
|
||||
await onSearchChange(newPageNumber)
|
||||
@@ -505,13 +501,13 @@ const selectableProjectTypes = computed(() => {
|
||||
if (instanceContext.value) {
|
||||
if (
|
||||
availableGameVersions.value.findIndex(
|
||||
(x) => x.version === instanceContext.value.metadata.game_version,
|
||||
(x) => x.version === instanceContext.value.game_version,
|
||||
) <= availableGameVersions.value.findIndex((x) => x.version === '1.13')
|
||||
) {
|
||||
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||
}
|
||||
|
||||
if (instanceContext.value.metadata.loader !== 'vanilla') {
|
||||
if (instanceContext.value.loader !== 'vanilla') {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
}
|
||||
} else {
|
||||
@@ -528,8 +524,6 @@ const showVersions = computed(
|
||||
)
|
||||
|
||||
const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.value))
|
||||
|
||||
onUnmounted(() => unlistenOffline())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -538,27 +532,19 @@ onUnmounted(() => unlistenOffline())
|
||||
<Card v-if="instanceContext" class="small-instance">
|
||||
<router-link :to="`/instance/${encodeURIComponent(instanceContext.path)}`" 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"
|
||||
:src="instanceContext.icon_path ? convertFileSrc(instanceContext.icon_path) : null"
|
||||
:alt="instanceContext.name"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="small-instance_info">
|
||||
<span class="title">{{
|
||||
instanceContext.metadata.name.length > 20
|
||||
? instanceContext.metadata.name.substring(0, 20) + '...'
|
||||
: instanceContext.metadata.name
|
||||
instanceContext.name.length > 20
|
||||
? instanceContext.name.substring(0, 20) + '...'
|
||||
: instanceContext.name
|
||||
}}</span>
|
||||
<span>
|
||||
{{
|
||||
instanceContext.metadata.loader.charAt(0).toUpperCase() +
|
||||
instanceContext.metadata.loader.slice(1)
|
||||
}}
|
||||
{{ instanceContext.metadata.game_version }}
|
||||
{{ instanceContext.loader.charAt(0).toUpperCase() + instanceContext.loader.slice(1) }}
|
||||
{{ instanceContext.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -598,7 +584,10 @@ onUnmounted(() => unlistenOffline())
|
||||
>
|
||||
<ClearIcon /> Clear filters
|
||||
</Button>
|
||||
<div v-if="isModProject || projectType === 'shader'" class="loaders">
|
||||
<div
|
||||
v-if="(isModProject && ignoreInstanceLoaders) || projectType === 'shader'"
|
||||
class="loaders"
|
||||
>
|
||||
<h2>Loaders</h2>
|
||||
<div v-for="loader in filteredLoaders" :key="loader">
|
||||
<SearchFilter
|
||||
@@ -693,9 +682,10 @@ onUnmounted(() => unlistenOffline())
|
||||
<input
|
||||
v-model="query"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
type="text"
|
||||
:placeholder="`Search ${projectType}s...`"
|
||||
@input="onSearchChange(1)"
|
||||
@input="debouncedSearchChange()"
|
||||
/>
|
||||
<Button class="r-btn" @click="() => clearSearch()">
|
||||
<XIcon />
|
||||
@@ -752,9 +742,6 @@ onUnmounted(() => unlistenOffline())
|
||||
loader.supported_project_types?.includes(projectType),
|
||||
),
|
||||
]"
|
||||
:confirm-modal="confirmModal"
|
||||
:mod-install-modal="modInstallModal"
|
||||
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||
:installed="result.installed"
|
||||
/>
|
||||
</section>
|
||||
@@ -768,9 +755,6 @@ onUnmounted(() => unlistenOffline())
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script setup>
|
||||
import { ref, onUnmounted, shallowRef, computed } from 'vue'
|
||||
import { ref, onUnmounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { offline_listener, profile_listener } from '@/helpers/events'
|
||||
import { profile_listener } from '@/helpers/events'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import dayjs from 'dayjs'
|
||||
import { isOffline } from '@/helpers/utils'
|
||||
import { get_search_results } from '@/helpers/cache.js'
|
||||
|
||||
const featuredModpacks = ref({})
|
||||
const featuredMods = ref({})
|
||||
@@ -19,45 +18,55 @@ const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
||||
|
||||
const recentInstances = shallowRef([])
|
||||
const recentInstances = ref([])
|
||||
|
||||
const offline = ref(await isOffline())
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const getInstances = async () => {
|
||||
const profiles = await list(true).catch(handleError)
|
||||
recentInstances.value = Object.values(profiles).sort((a, b) => {
|
||||
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
|
||||
const profiles = await list().catch(handleError)
|
||||
|
||||
recentInstances.value = profiles.sort((a, b) => {
|
||||
const dateA = dayjs(a.last_played ?? 0)
|
||||
const dateB = dayjs(b.last_played ?? 0)
|
||||
|
||||
if (dateA.isSame(dateB)) {
|
||||
return a.name.localeCompare(b.name)
|
||||
}
|
||||
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
let filters = []
|
||||
for (const instance of recentInstances.value) {
|
||||
if (instance.metadata.linked_data && instance.metadata.linked_data.project_id) {
|
||||
filters.push(`NOT"project_id"="${instance.metadata.linked_data.project_id}"`)
|
||||
if (instance.linked_data && instance.linked_data.project_id) {
|
||||
filters.push(`NOT"project_id"="${instance.linked_data.project_id}"`)
|
||||
}
|
||||
}
|
||||
filter.value = filters.join(' AND ')
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'featured modpacks',
|
||||
offline.value,
|
||||
const response = await get_search_results(
|
||||
`?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
)
|
||||
|
||||
if (response) {
|
||||
featuredModpacks.value = response.hits
|
||||
featuredModpacks.value = response.result.hits
|
||||
} else {
|
||||
featuredModpacks.value = []
|
||||
}
|
||||
}
|
||||
const getFeaturedMods = async () => {
|
||||
const response = await useFetch(
|
||||
'https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows',
|
||||
'featured mods',
|
||||
offline.value,
|
||||
)
|
||||
const response = await get_search_results('?facets=[["project_type:mod"]]&limit=10&index=follows')
|
||||
|
||||
if (response) {
|
||||
featuredMods.value = response.hits
|
||||
featuredMods.value = response.result.hits
|
||||
} else {
|
||||
featuredModpacks.value = []
|
||||
}
|
||||
@@ -69,14 +78,8 @@ await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
|
||||
|
||||
const unlistenProfile = await profile_listener(async (e) => {
|
||||
await getInstances()
|
||||
if (e.event === 'created' || e.event === 'removed') {
|
||||
await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
|
||||
}
|
||||
})
|
||||
|
||||
const unlistenOffline = await offline_listener(async (b) => {
|
||||
offline.value = b
|
||||
if (!b) {
|
||||
if (e.event === 'added' || e.event === 'created' || e.event === 'removed') {
|
||||
await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
|
||||
}
|
||||
})
|
||||
@@ -92,7 +95,6 @@ const total = computed(() => {
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProfile()
|
||||
unlistenOffline()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -105,6 +107,7 @@ onUnmounted(() => {
|
||||
label: 'Jump back in',
|
||||
route: '/library',
|
||||
instances: recentInstances,
|
||||
instance: true,
|
||||
downloaded: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,34 +4,33 @@ import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { offline_listener, profile_listener } from '@/helpers/events.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { PlusIcon } from '@modrinth/assets'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { NewInstanceImage } from '@/assets/icons'
|
||||
import { isOffline } from '@/helpers/utils'
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
||||
|
||||
const profiles = await list(true).catch(handleError)
|
||||
const instances = shallowRef(Object.values(profiles))
|
||||
const instances = shallowRef(await list().catch(handleError))
|
||||
|
||||
const offline = ref(await isOffline())
|
||||
const unlistenOffline = await offline_listener((b) => {
|
||||
offline.value = b
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const unlistenProfile = await profile_listener(async () => {
|
||||
const profiles = await list(true).catch(handleError)
|
||||
instances.value = Object.values(profiles)
|
||||
instances.value = await list().catch(handleError)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
unlistenProfile()
|
||||
unlistenOffline()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, UpdatedIcon } from '@
|
||||
import { Card, Slider, DropdownSelect, Toggle, Modal, Button } from '@modrinth/ui'
|
||||
import { handleError, useTheming } from '@/store/state'
|
||||
import { is_dir_writeable, change_config_dir, get, set } from '@/helpers/settings'
|
||||
import { get_max_memory } from '@/helpers/jre'
|
||||
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
|
||||
import { get as getCreds, logout } from '@/helpers/mr_auth.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
|
||||
@@ -12,6 +12,7 @@ import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/m
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
|
||||
const pageOptions = ['Home', 'Library']
|
||||
|
||||
@@ -22,8 +23,8 @@ const version = await getVersion()
|
||||
const accessSettings = async () => {
|
||||
const settings = await get()
|
||||
|
||||
settings.javaArgs = settings.custom_java_args.join(' ')
|
||||
settings.envArgs = settings.custom_env_args.map((x) => x.join('=')).join(' ')
|
||||
settings.launchArgs = settings.extra_launch_args.join(' ')
|
||||
settings.envVars = settings.custom_env_vars.map((x) => x.join('=')).join(' ')
|
||||
|
||||
return settings
|
||||
}
|
||||
@@ -31,7 +32,8 @@ const accessSettings = async () => {
|
||||
const fetchSettings = await accessSettings().catch(handleError)
|
||||
|
||||
const settings = ref(fetchSettings)
|
||||
const settingsDir = ref(settings.value.loaded_config_dir)
|
||||
// const settingsDir = ref(settings.value.loaded_config_dir)
|
||||
|
||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
||||
|
||||
watch(
|
||||
@@ -43,26 +45,14 @@ watch(
|
||||
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
|
||||
if (setSettings.opt_out_analytics) {
|
||||
if (setSettings.telemetry) {
|
||||
mixpanel_opt_out_tracking()
|
||||
} else {
|
||||
mixpanel_opt_in_tracking()
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(setSettings.java_globals)) {
|
||||
if (value?.path === '') {
|
||||
value.path = undefined
|
||||
}
|
||||
|
||||
if (value?.path) {
|
||||
value.path = value.path.replace('java.exe', 'javaw.exe')
|
||||
}
|
||||
|
||||
console.log(`${key}: ${value}`)
|
||||
}
|
||||
|
||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_args = setSettings.envArgs
|
||||
setSettings.extra_launch_args = setSettings.launchArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_vars = setSettings.envVars
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
@@ -78,22 +68,49 @@ watch(
|
||||
setSettings.hooks.post_exit = null
|
||||
}
|
||||
|
||||
if (!setSettings.custom_dir) {
|
||||
setSettings.custom_dir = null
|
||||
}
|
||||
|
||||
await set(setSettings)
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
const credentials = ref(await getCreds().catch(handleError))
|
||||
const javaVersions = ref(await get_java_versions().catch(handleError))
|
||||
async function updateJavaVersion(version) {
|
||||
if (version?.path === '') {
|
||||
version.path = undefined
|
||||
}
|
||||
|
||||
if (version?.path) {
|
||||
version.path = version.path.replace('java.exe', 'javaw.exe')
|
||||
}
|
||||
|
||||
await set_java_version(version).catch(handleError)
|
||||
}
|
||||
|
||||
async function fetchCredentials() {
|
||||
const creds = await getCreds().catch(handleError)
|
||||
console.log(creds)
|
||||
if (creds && creds.user_id) {
|
||||
creds.user = await get_user(creds.user_id).catch(handleError)
|
||||
}
|
||||
credentials.value = creds
|
||||
}
|
||||
|
||||
const credentials = ref()
|
||||
await fetchCredentials()
|
||||
|
||||
const loginScreenModal = ref()
|
||||
|
||||
async function logOut() {
|
||||
await logout().catch(handleError)
|
||||
credentials.value = await getCreds().catch(handleError)
|
||||
await fetchCredentials()
|
||||
}
|
||||
|
||||
async function signInAfter() {
|
||||
loginScreenModal.value.hide()
|
||||
credentials.value = await getCreds().catch(handleError)
|
||||
await fetchCredentials()
|
||||
}
|
||||
|
||||
async function findLauncherDir() {
|
||||
@@ -103,24 +120,10 @@ async function findLauncherDir() {
|
||||
title: 'Select a new app directory',
|
||||
})
|
||||
|
||||
const writeable = await is_dir_writeable(newDir)
|
||||
|
||||
if (!writeable) {
|
||||
handleError('The selected directory does not have proper permissions for write access.')
|
||||
return
|
||||
}
|
||||
|
||||
if (newDir) {
|
||||
settingsDir.value = newDir
|
||||
await refreshDir()
|
||||
settings.value.custom_dir = newDir
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshDir() {
|
||||
await change_config_dir(settingsDir.value).catch(handleError)
|
||||
settings.value = await accessSettings().catch(handleError)
|
||||
settingsDir.value = settings.value.loaded_config_dir
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -131,13 +134,7 @@ async function refreshDir() {
|
||||
<span class="label__title size-card-header">General settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<Modal
|
||||
ref="loginScreenModal"
|
||||
class="login-screen-modal"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<ModrinthLoginScreen :modal="true" :prev-page="signInAfter" :next-page="signInAfter" />
|
||||
</Modal>
|
||||
<ModrinthLoginScreen ref="loginScreenModal" :callback="signInAfter" />
|
||||
<div class="adjacent-input">
|
||||
<label for="theme">
|
||||
<span class="label__title">Manage account</span>
|
||||
@@ -164,15 +161,11 @@ async function refreshDir() {
|
||||
<div class="app-directory">
|
||||
<div class="iconified-input">
|
||||
<BoxIcon />
|
||||
<input id="appDir" v-model="settingsDir" type="text" class="input" />
|
||||
<input id="appDir" v-model="settings.custom_dir" type="text" class="input" />
|
||||
<Button class="r-btn" @click="findLauncherDir">
|
||||
<FolderSearchIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Button large @click="refreshDir">
|
||||
<UpdatedIcon />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
@@ -230,11 +223,11 @@ async function refreshDir() {
|
||||
</label>
|
||||
<Toggle
|
||||
id="minimize-launcher"
|
||||
:model-value="settings.hide_on_process"
|
||||
:checked="settings.hide_on_process"
|
||||
:model-value="settings.hide_on_process_start"
|
||||
:checked="settings.hide_on_process_start"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.hide_on_process = e
|
||||
settings.hide_on_process_start = e
|
||||
}
|
||||
"
|
||||
/>
|
||||
@@ -285,10 +278,11 @@ async function refreshDir() {
|
||||
<div class="adjacent-input">
|
||||
<label for="max-downloads">
|
||||
<span class="label__title">Maximum concurrent downloads</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can download at the same time. Set this to a
|
||||
lower value if you have a poor internet connection.</span
|
||||
>
|
||||
<span class="label__description">
|
||||
The maximum amount of files the launcher can download at the same time. Set this to a
|
||||
lower value if you have a poor internet connection. (app restart required to take
|
||||
effect)
|
||||
</span>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-downloads"
|
||||
@@ -302,10 +296,11 @@ async function refreshDir() {
|
||||
<div class="adjacent-input">
|
||||
<label for="max-writes">
|
||||
<span class="label__title">Maximum concurrent writes</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can write to the disk at once. Set this to a
|
||||
lower value if you are frequently getting I/O errors.</span
|
||||
>
|
||||
<span class="label__description">
|
||||
The maximum amount of files the launcher can write to the disk at once. Set this to a
|
||||
lower value if you are frequently getting I/O errors. (app restart required to take
|
||||
effect)
|
||||
</span>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-writes"
|
||||
@@ -324,37 +319,38 @@ async function refreshDir() {
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="opt-out-analytics">
|
||||
<span class="label__title">Disable analytics</span>
|
||||
<span class="label__title">Telemetry</span>
|
||||
<span class="label__description">
|
||||
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||
customize your experience. By enabling this option, you opt out and your data will no
|
||||
customize your experience. By disabling this option, you opt out and your data will no
|
||||
longer be collected.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="opt-out-analytics"
|
||||
:model-value="settings.opt_out_analytics"
|
||||
:checked="settings.opt_out_analytics"
|
||||
:model-value="settings.telemetry"
|
||||
:checked="settings.telemetry"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
settings.opt_out_analytics = e
|
||||
settings.telemetry = e
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="disable-discord-rpc">
|
||||
<span class="label__title">Disable Discord RPC</span>
|
||||
<span class="label__title">Discord RPC</span>
|
||||
<span class="label__description">
|
||||
Disables the Discord Rich Presence integration. 'Modrinth' will no longer show up as a
|
||||
game or app you are using on your Discord profile. This does not disable any
|
||||
instance-specific Discord Rich Presence integrations, such as those added by mods.
|
||||
Manages the Discord Rich Presence integration. Disabling this will cause 'Modrinth' to
|
||||
no longer show up as a game or app you are using on your Discord profile. This does not
|
||||
disable any instance-specific Discord Rich Presence integrations, such as those added by
|
||||
mods. (app restart required to take effect)
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="disable-discord-rpc"
|
||||
v-model="settings.disable_discord_rpc"
|
||||
:checked="settings.disable_discord_rpc"
|
||||
v-model="settings.discord_rpc"
|
||||
:checked="settings.discord_rpc"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -364,25 +360,24 @@ async function refreshDir() {
|
||||
<span class="label__title size-card-header">Java settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<label for="java-21">
|
||||
<span class="label__title">Java 21 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-17" v-model="settings.java_globals.JAVA_21" :version="21" />
|
||||
<label for="java-17">
|
||||
<span class="label__title">Java 17 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-17" v-model="settings.java_globals.JAVA_17" :version="17" />
|
||||
<label for="java-8">
|
||||
<span class="label__title">Java 8 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-8" v-model="settings.java_globals.JAVA_8" :version="8" />
|
||||
<template v-for="version in [21, 17, 8]">
|
||||
<label :for="'java-' + version">
|
||||
<span class="label__title">Java {{ version }} location</span>
|
||||
</label>
|
||||
<JavaSelector
|
||||
:id="'java-selector-' + version"
|
||||
v-model="javaVersions[version]"
|
||||
:version="version"
|
||||
@update:model-value="updateJavaVersion"
|
||||
/>
|
||||
</template>
|
||||
<hr class="card-divider" />
|
||||
<label for="java-args">
|
||||
<span class="label__title">Java arguments</span>
|
||||
</label>
|
||||
<input
|
||||
id="java-args"
|
||||
v-model="settings.javaArgs"
|
||||
v-model="settings.launchArgs"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
@@ -393,7 +388,7 @@ async function refreshDir() {
|
||||
</label>
|
||||
<input
|
||||
id="env-vars"
|
||||
v-model="settings.envArgs"
|
||||
v-model="settings.envVars"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
@@ -526,7 +521,7 @@ async function refreshDir() {
|
||||
<div>
|
||||
<label>
|
||||
<span class="label__title">App version</span>
|
||||
<span class="label__description">Theseus v{{ version }} </span>
|
||||
<span class="label__description">Modrinth App v{{ version }} </span>
|
||||
</label>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -551,16 +546,6 @@ async function refreshDir() {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
:deep(.login-screen-modal) {
|
||||
.modal-container .modal-body {
|
||||
width: auto;
|
||||
|
||||
.content {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-directory {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -2,20 +2,10 @@
|
||||
<div class="instance-container">
|
||||
<div class="side-cards">
|
||||
<Card class="instance-card" @contextmenu.prevent.stop="handleRightClick">
|
||||
<Avatar
|
||||
size="lg"
|
||||
:src="
|
||||
!instance.metadata.icon ||
|
||||
(instance.metadata.icon && instance.metadata.icon.startsWith('http'))
|
||||
? instance.metadata.icon
|
||||
: convertFileSrc(instance.metadata?.icon)
|
||||
"
|
||||
/>
|
||||
<Avatar size="lg" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
|
||||
<div class="instance-info">
|
||||
<h2 class="name">{{ instance.metadata.name }}</h2>
|
||||
<span class="metadata">
|
||||
{{ instance.metadata.loader }} {{ instance.metadata.game_version }}
|
||||
</span>
|
||||
<h2 class="name">{{ instance.name }}</h2>
|
||||
<span class="metadata"> {{ instance.loader }} {{ instance.game_version }} </span>
|
||||
</div>
|
||||
<span class="button-group">
|
||||
<Button v-if="instance.install_stage !== 'installed'" disabled class="instance-button">
|
||||
@@ -26,7 +16,6 @@
|
||||
color="danger"
|
||||
class="instance-button"
|
||||
@click="stopInstance('InstancePage')"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
Stop
|
||||
@@ -36,7 +25,6 @@
|
||||
color="primary"
|
||||
class="instance-button"
|
||||
@click="startInstance('InstancePage')"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<PlayIcon />
|
||||
Play
|
||||
@@ -135,22 +123,20 @@ import {
|
||||
CheckCircleIcon,
|
||||
UpdatedIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { get, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
kill_by_uuid,
|
||||
} from '@/helpers/process'
|
||||
import { offline_listener, process_listener, profile_listener } from '@/helpers/events'
|
||||
import { get, kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
||||
import { isOffline, showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useFetch } from '@/helpers/fetch'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { get_project, get_version_many } from '@/helpers/cache.js'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -161,72 +147,71 @@ const instance = ref(await get(route.params.id).catch(handleError))
|
||||
|
||||
breadcrumbs.setName(
|
||||
'Instance',
|
||||
instance.value.metadata.name.length > 40
|
||||
? instance.value.metadata.name.substring(0, 40) + '...'
|
||||
: instance.value.metadata.name,
|
||||
instance.value.name.length > 40
|
||||
? instance.value.name.substring(0, 40) + '...'
|
||||
: instance.value.name,
|
||||
)
|
||||
|
||||
breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
name: instance.value.name,
|
||||
link: route.path,
|
||||
query: route.query,
|
||||
})
|
||||
|
||||
const offline = ref(await isOffline())
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const loadingBar = useLoading()
|
||||
|
||||
const uuid = ref(null)
|
||||
const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
const options = ref(null)
|
||||
|
||||
const startInstance = async (context) => {
|
||||
loading.value = true
|
||||
uuid.value = await run(route.params.id).catch(handleSevereError)
|
||||
run(route.params.id).catch(handleSevereError)
|
||||
loading.value = false
|
||||
playing.value = true
|
||||
|
||||
mixpanel_track('InstanceStart', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningPaths = await get_all_running_profile_paths().catch(handleError)
|
||||
if (runningPaths.includes(instance.value.path)) {
|
||||
playing.value = true
|
||||
return
|
||||
}
|
||||
const runningProcesses = await get_by_profile_path(route.params.id).catch(handleError)
|
||||
|
||||
playing.value = false
|
||||
uuid.value = null
|
||||
playing.value = runningProcesses.length > 0
|
||||
}
|
||||
|
||||
// Get information on associated modrinth versions, if any
|
||||
const modrinthVersions = ref([])
|
||||
if (!(await isOffline()) && instance.value.metadata.linked_data?.project_id) {
|
||||
modrinthVersions.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${instance.value.metadata.linked_data.project_id}/version`,
|
||||
'project',
|
||||
)
|
||||
if (!offline.value && instance.value.linked_data && instance.value.linked_data.project_id) {
|
||||
const project = await get_project(instance.value.linked_data.project_id).catch(handleError)
|
||||
|
||||
if (project && project.versions) {
|
||||
modrinthVersions.value = (await get_version_many(project.versions).catch(handleError)).sort(
|
||||
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await checkProcess()
|
||||
|
||||
const stopInstance = async (context) => {
|
||||
playing.value = false
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(instance.value.path).catch(handleError)
|
||||
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError)
|
||||
await kill(route.params.id).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
@@ -271,7 +256,7 @@ const handleOptionsClick = async (args) => {
|
||||
break
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
path: `/browse/${instance.value.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
path: `/browse/${instance.value.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: route.params.id },
|
||||
})
|
||||
break
|
||||
@@ -302,17 +287,12 @@ const unlistenProfiles = await profile_listener(async (event) => {
|
||||
})
|
||||
|
||||
const unlistenProcesses = await process_listener((e) => {
|
||||
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
||||
})
|
||||
|
||||
const unlistenOffline = await offline_listener((b) => {
|
||||
offline.value = b
|
||||
if (e.event === 'finished' && e.profile_path_id === route.params.id) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProcesses()
|
||||
unlistenProfiles()
|
||||
unlistenOffline()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch
|
||||
import dayjs from 'dayjs'
|
||||
import isToday from 'dayjs/plugin/isToday'
|
||||
import isYesterday from 'dayjs/plugin/isYesterday'
|
||||
import { get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import { get_by_profile_path } from '@/helpers/process.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { process_listener } from '@/helpers/events.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
@@ -209,9 +209,9 @@ const processedLogs = computed(() => {
|
||||
|
||||
async function getLiveStdLog() {
|
||||
if (route.params.id) {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
const processes = await get_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
if (processes.length === 0) {
|
||||
returnValue = emptyText.join('\n')
|
||||
} else {
|
||||
const logCursor = await get_latest_log_cursor(
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
<div class="markdown-body">
|
||||
<p>
|
||||
Are you sure you want to remove
|
||||
<strong>{{ functionValues.length }} project(s)</strong> from {{ instance.metadata.name }}?
|
||||
<strong>{{ functionValues.length }} project(s)</strong> from {{ instance.name }}?
|
||||
<br />
|
||||
This action <strong>cannot</strong> be undone.
|
||||
</p>
|
||||
@@ -304,7 +304,7 @@
|
||||
>{{ Array.from(projects.values()).filter((x) => x.disabled).length }} disabled
|
||||
project(s)</strong
|
||||
>
|
||||
from {{ instance.metadata.name }}?
|
||||
from {{ instance.name }}?
|
||||
<br />
|
||||
This action <strong>cannot</strong> be undone.
|
||||
</p>
|
||||
@@ -326,7 +326,7 @@
|
||||
/>
|
||||
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
|
||||
<ModpackVersionModal
|
||||
v-if="instance.metadata.linked_data"
|
||||
v-if="instance.linked_data"
|
||||
ref="modpackVersionModal"
|
||||
:instance="instance"
|
||||
:versions="props.versions"
|
||||
@@ -364,6 +364,7 @@ import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
add_project_from_path,
|
||||
get,
|
||||
get_projects,
|
||||
remove_project,
|
||||
toggle_disable_project,
|
||||
update_all,
|
||||
@@ -372,12 +373,18 @@ import {
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
import AddContentButton from '@/components/ui/AddContentButton.vue'
|
||||
import {
|
||||
get_organization_many,
|
||||
get_project_many,
|
||||
get_team_many,
|
||||
get_version_many,
|
||||
} from '@/helpers/cache.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -404,65 +411,102 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const projects = ref([])
|
||||
const selectionMap = ref(new Map())
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (
|
||||
event.profile_path_id === props.instance.path &&
|
||||
event.event === 'synced' &&
|
||||
props.instance.install_stage !== 'pack_installing'
|
||||
) {
|
||||
await initProjects()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProfiles()
|
||||
})
|
||||
|
||||
const showingOptions = ref(false)
|
||||
const isPackLocked = computed(() => {
|
||||
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked
|
||||
return props.instance.linked_data && props.instance.linked_data.locked
|
||||
})
|
||||
const canUpdatePack = computed(() => {
|
||||
if (!props.instance.metadata.linked_data) return false
|
||||
return props.instance.metadata.linked_data.version_id !== props.instance.modrinth_update_version
|
||||
if (!props.instance.linked_data || !props.versions || !props.versions[0]) return false
|
||||
return props.instance.linked_data.version_id !== props.versions[0].id
|
||||
})
|
||||
const exportModal = ref(null)
|
||||
|
||||
const initProjects = (initInstance) => {
|
||||
projects.value = []
|
||||
if (!initInstance || !initInstance.projects) return
|
||||
for (const [path, project] of Object.entries(initInstance.projects)) {
|
||||
if (project.metadata.type === 'modrinth' && !props.offline) {
|
||||
let owner = project.metadata.members.find((x) => x.role === 'Owner')
|
||||
projects.value.push({
|
||||
const projects = ref([])
|
||||
const selectionMap = ref(new Map())
|
||||
|
||||
const initProjects = async () => {
|
||||
const newProjects = []
|
||||
|
||||
const profileProjects = await get_projects(props.instance.path)
|
||||
const fetchProjects = []
|
||||
const fetchVersions = []
|
||||
|
||||
for (const value of Object.values(profileProjects)) {
|
||||
if (value.metadata) {
|
||||
fetchProjects.push(value.metadata.project_id)
|
||||
fetchVersions.push(value.metadata.version_id)
|
||||
}
|
||||
}
|
||||
|
||||
const [modrinthProjects, modrinthVersions] = await Promise.all([
|
||||
await get_project_many(fetchProjects).catch(handleError),
|
||||
await get_version_many(fetchVersions).catch(handleError),
|
||||
])
|
||||
|
||||
const [modrinthTeams, modrinthOrganizations] = await Promise.all([
|
||||
await get_team_many(modrinthProjects.map((x) => x.team)).catch(handleError),
|
||||
await get_organization_many(
|
||||
modrinthProjects.map((x) => x.organization).filter((x) => !!x),
|
||||
).catch(handleError),
|
||||
])
|
||||
|
||||
for (const [path, file] of Object.entries(profileProjects)) {
|
||||
if (file.metadata) {
|
||||
const project = modrinthProjects.find((x) => file.metadata.project_id === x.id)
|
||||
const version = modrinthVersions.find((x) => file.metadata.version_id === x.id)
|
||||
const org = project.organization
|
||||
? modrinthOrganizations.find((x) => x.id === project.organization)
|
||||
: null
|
||||
|
||||
const team = modrinthTeams.find((x) => x[0].team_id === project.team)
|
||||
|
||||
let owner = org ? org.name : team.find((x) => x.is_owner).user.username
|
||||
|
||||
newProjects.push({
|
||||
path,
|
||||
name: project.metadata.project.title,
|
||||
slug: project.metadata.project.slug,
|
||||
author: owner ? owner.user.username : null,
|
||||
version: project.metadata.version.version_number,
|
||||
file_name: project.file_name,
|
||||
icon: project.metadata.project.icon_url,
|
||||
disabled: project.disabled,
|
||||
updateVersion: project.metadata.update_version,
|
||||
outdated: !!project.metadata.update_version,
|
||||
project_type: project.metadata.project.project_type,
|
||||
id: project.metadata.project.id,
|
||||
})
|
||||
} else if (project.metadata.type === 'inferred') {
|
||||
projects.value.push({
|
||||
path,
|
||||
name: project.metadata.title ?? project.file_name,
|
||||
author: project.metadata.authors[0],
|
||||
version: project.metadata.version,
|
||||
file_name: project.file_name,
|
||||
icon: project.metadata.icon ? convertFileSrc(project.metadata.icon) : null,
|
||||
disabled: project.disabled,
|
||||
outdated: false,
|
||||
project_type: project.metadata.project_type,
|
||||
name: project.title,
|
||||
slug: project.slug,
|
||||
author: owner,
|
||||
version: version.version_number,
|
||||
file_name: file.file_name,
|
||||
icon: project.icon_url,
|
||||
disabled: file.file_name.endsWith('.disabled'),
|
||||
updateVersion: file.update_version_id,
|
||||
outdated: !!file.update_version_id,
|
||||
project_type: project.project_type,
|
||||
id: project.id,
|
||||
})
|
||||
} else {
|
||||
projects.value.push({
|
||||
newProjects.push({
|
||||
path,
|
||||
name: project.file_name,
|
||||
name: file.file_name.replace('.disabled', ''),
|
||||
author: '',
|
||||
version: null,
|
||||
file_name: project.file_name,
|
||||
file_name: file.file_name,
|
||||
icon: null,
|
||||
disabled: project.disabled,
|
||||
disabled: file.file_name.endsWith('.disabled'),
|
||||
outdated: false,
|
||||
project_type: null,
|
||||
project_type: file.project_type,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
projects.value = newProjects
|
||||
|
||||
const newSelectionMap = new Map()
|
||||
for (const project of projects.value) {
|
||||
newSelectionMap.set(
|
||||
@@ -475,22 +519,7 @@ const initProjects = (initInstance) => {
|
||||
}
|
||||
selectionMap.value = newSelectionMap
|
||||
}
|
||||
|
||||
initProjects(props.instance)
|
||||
|
||||
watch(
|
||||
() => props.instance.projects,
|
||||
() => {
|
||||
initProjects(props.instance)
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.offline,
|
||||
() => {
|
||||
if (props.instance) initProjects(props.instance)
|
||||
},
|
||||
)
|
||||
await initProjects()
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
@@ -633,8 +662,8 @@ const updateAll = async () => {
|
||||
}
|
||||
|
||||
mixpanel_track('InstanceUpdateAll', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
count: setProjects.length,
|
||||
selected: selected.value.length > 1,
|
||||
})
|
||||
@@ -659,8 +688,8 @@ const updateProject = async (mod) => {
|
||||
mod.updateVersion = null
|
||||
|
||||
mixpanel_track('InstanceProjectUpdate', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
@@ -686,8 +715,8 @@ const toggleDisableMod = async (mod) => {
|
||||
mod.path = newPath
|
||||
mod.disabled = !mod.disabled
|
||||
mixpanel_track('InstanceProjectDisable', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
@@ -707,8 +736,8 @@ const removeMod = async (mod) => {
|
||||
projects.value = projects.value.filter((x) => mod.path !== x.path)
|
||||
|
||||
mixpanel_track('InstanceProjectRemove', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
@@ -820,7 +849,7 @@ watch(selectAll, () => {
|
||||
const unlisten = await listen('tauri://file-drop', async (event) => {
|
||||
for (const file of event.payload) {
|
||||
if (file.endsWith('.mrpack')) continue
|
||||
await add_project_from_path(props.instance.path, file, 'mod').catch(handleError)
|
||||
await add_project_from_path(props.instance.path, file).catch(handleError)
|
||||
}
|
||||
initProjects(await get(props.instance.path).catch(handleError))
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="action_disabled" @click="unlockProfile">
|
||||
<button class="btn btn-danger" @click="unlockProfile">
|
||||
<LockIcon />
|
||||
Unlock
|
||||
</button>
|
||||
@@ -50,7 +50,7 @@
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="action_disabled" @click="unpairProfile">
|
||||
<button class="btn btn-danger" @click="unpairProfile">
|
||||
<XIcon />
|
||||
Unpair
|
||||
</button>
|
||||
@@ -117,21 +117,13 @@
|
||||
<span class="label__title">Icon</span>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<Avatar
|
||||
:src="!icon || (icon && icon.startsWith('http')) ? icon : convertFileSrc(icon)"
|
||||
size="md"
|
||||
class="project__icon"
|
||||
/>
|
||||
<Avatar :src="icon ? convertFileSrc(icon) : icon" size="md" class="project__icon" />
|
||||
<div class="input-stack">
|
||||
<button id="instance-icon" class="btn" @click="setIcon">
|
||||
<UploadIcon />
|
||||
Select icon
|
||||
</button>
|
||||
<button
|
||||
:disabled="!(!icon || (icon && icon.startsWith('http')) ? icon : convertFileSrc(icon))"
|
||||
class="btn"
|
||||
@click="resetIcon"
|
||||
>
|
||||
<button :disabled="!icon" class="btn" @click="resetIcon">
|
||||
<TrashIcon />
|
||||
Remove icon
|
||||
</button>
|
||||
@@ -147,7 +139,7 @@
|
||||
autocomplete="off"
|
||||
maxlength="80"
|
||||
type="text"
|
||||
:disabled="instance.metadata.linked_data"
|
||||
:disabled="instance.linked_data"
|
||||
/>
|
||||
|
||||
<div class="adjacent-input">
|
||||
@@ -358,7 +350,7 @@
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card v-if="instance.metadata.linked_data">
|
||||
<Card v-if="instance.linked_data">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Modpack</span>
|
||||
@@ -366,9 +358,7 @@
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="general-modpack-info">
|
||||
<span class="label__description">
|
||||
<strong>Modpack: </strong> {{ instance.metadata.name }}
|
||||
</span>
|
||||
<span class="label__description"> <strong>Modpack: </strong> {{ instance.name }} </span>
|
||||
<span class="label__description">
|
||||
<strong>Version: </strong>
|
||||
{{
|
||||
@@ -414,7 +404,7 @@
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div v-if="props.instance.metadata.linked_data.project_id" class="adjacent-input">
|
||||
<div v-if="instance.linked_data.project_id" class="adjacent-input">
|
||||
<label for="change-modpack-version">
|
||||
<span class="label__title">Change modpack version</span>
|
||||
<span class="label__description">
|
||||
@@ -502,7 +492,7 @@
|
||||
</div>
|
||||
</Card>
|
||||
<ModpackVersionModal
|
||||
v-if="instance.metadata.linked_data"
|
||||
v-if="instance.linked_data"
|
||||
ref="modpackVersionModal"
|
||||
:instance="instance"
|
||||
:versions="props.versions"
|
||||
@@ -553,12 +543,7 @@ import { get } from '@/helpers/settings.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import {
|
||||
get_fabric_versions,
|
||||
get_forge_versions,
|
||||
get_neoforge_versions,
|
||||
get_quilt_versions,
|
||||
} from '@/helpers/metadata.js'
|
||||
import { get_loader_versions } from '@/helpers/metadata.js'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
@@ -587,17 +572,17 @@ const props = defineProps({
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const title = ref(props.instance.metadata.name)
|
||||
const icon = ref(props.instance.metadata.icon)
|
||||
const groups = ref(props.instance.metadata.groups)
|
||||
const title = ref(props.instance.name)
|
||||
const icon = ref(props.instance.icon_path)
|
||||
const groups = ref(props.instance.groups)
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
|
||||
const instancesList = Object.values(await list(true))
|
||||
const instancesList = await list()
|
||||
const availableGroups = ref([
|
||||
...new Set(
|
||||
instancesList.reduce((acc, obj) => {
|
||||
return acc.concat(obj.metadata.groups)
|
||||
return acc.concat(obj.groups)
|
||||
}, []),
|
||||
),
|
||||
])
|
||||
@@ -632,18 +617,18 @@ const globalSettings = await get().catch(handleError)
|
||||
const modalConfirmUnlock = ref(null)
|
||||
const modalConfirmUnpair = ref(null)
|
||||
|
||||
const javaSettings = props.instance.java ?? {}
|
||||
|
||||
const overrideJavaInstall = ref(!!javaSettings.override_version)
|
||||
const overrideJavaInstall = ref(!!props.instance.java_path)
|
||||
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
||||
const javaInstall = ref(optimalJava ?? javaSettings.override_version ?? { path: '', version: '' })
|
||||
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
||||
|
||||
const overrideJavaArgs = ref(!!javaSettings.extra_arguments)
|
||||
const javaArgs = ref((javaSettings.extra_arguments ?? globalSettings.custom_java_args).join(' '))
|
||||
const overrideJavaArgs = ref(!!props.instance.extra_launch_args)
|
||||
const javaArgs = ref(
|
||||
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
||||
)
|
||||
|
||||
const overrideEnvVars = ref(!!javaSettings.custom_env_args)
|
||||
const overrideEnvVars = ref(!!props.instance.custom_env_vars)
|
||||
const envVars = ref(
|
||||
(javaSettings.custom_env_args ?? globalSettings.custom_env_args)
|
||||
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
||||
.map((x) => x.join('='))
|
||||
.join(' '),
|
||||
)
|
||||
@@ -652,18 +637,22 @@ const overrideMemorySettings = ref(!!props.instance.memory)
|
||||
const memory = ref(props.instance.memory ?? globalSettings.memory)
|
||||
const maxMemory = Math.floor((await get_max_memory().catch(handleError)) / 1024)
|
||||
|
||||
const overrideWindowSettings = ref(!!props.instance.resolution || !!props.instance.fullscreen)
|
||||
const resolution = ref(props.instance.resolution ?? globalSettings.game_resolution)
|
||||
const overrideHooks = ref(!!props.instance.hooks)
|
||||
const overrideWindowSettings = ref(
|
||||
!!props.instance.game_resolution || !!props.instance.force_fullscreen,
|
||||
)
|
||||
const resolution = ref(props.instance.game_resolution ?? globalSettings.game_resolution)
|
||||
const overrideHooks = ref(
|
||||
props.instance.hooks.pre_launch || props.instance.hooks.wrapper || props.instance.hooks.post_exit,
|
||||
)
|
||||
const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
|
||||
|
||||
const fullscreenSetting = ref(!!props.instance.fullscreen)
|
||||
const fullscreenSetting = ref(!!props.instance.force_fullscreen)
|
||||
|
||||
const unlinkModpack = ref(false)
|
||||
|
||||
const inProgress = ref(false)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.version_id)
|
||||
const installedVersion = computed(() => props.instance?.linked_data?.version_id)
|
||||
const installedVersionData = computed(() => {
|
||||
if (!installedVersion.value) return null
|
||||
return props.versions.find((version) => version.id === installedVersion.value)
|
||||
@@ -706,34 +695,29 @@ const getLocalVersion = (path) => {
|
||||
|
||||
const editProfileObject = computed(() => {
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
name: title.value.trim().substring(0, 32) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.metadata.loader_version,
|
||||
linked_data: props.instance.metadata.linked_data,
|
||||
},
|
||||
name: title.value.trim().substring(0, 32) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.loader_version,
|
||||
linked_data: props.instance.linked_data,
|
||||
java: {},
|
||||
hooks: {},
|
||||
}
|
||||
|
||||
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',
|
||||
)
|
||||
editProfile.java_path = javaInstall.value.path.replace('java.exe', 'javaw.exe')
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideJavaArgs.value) {
|
||||
if (javaArgs.value !== '') {
|
||||
editProfile.java.extra_arguments = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideEnvVars.value) {
|
||||
if (envVars.value !== '') {
|
||||
editProfile.java.custom_env_args = envVars.value
|
||||
editProfile.custom_env_vars = envVars.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
@@ -746,10 +730,10 @@ const editProfileObject = computed(() => {
|
||||
}
|
||||
|
||||
if (overrideWindowSettings.value) {
|
||||
editProfile.fullscreen = fullscreenSetting.value
|
||||
editProfile.force_fullscreen = fullscreenSetting.value
|
||||
|
||||
if (!fullscreenSetting.value) {
|
||||
editProfile.resolution = resolution.value
|
||||
editProfile.game_resolution = resolution.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,10 +742,10 @@ const editProfileObject = computed(() => {
|
||||
}
|
||||
|
||||
if (unlinkModpack.value) {
|
||||
editProfile.metadata.linked_data = null
|
||||
editProfile.linked_data = null
|
||||
}
|
||||
|
||||
breadcrumbs.setName('Instance', editProfile.metadata.name)
|
||||
breadcrumbs.setName('Instance', editProfile.name)
|
||||
|
||||
return editProfile
|
||||
})
|
||||
@@ -771,8 +755,8 @@ const repairing = ref(false)
|
||||
async function duplicateProfile() {
|
||||
await duplicate(props.instance.path).catch(handleError)
|
||||
mixpanel_track('InstanceDuplicate', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -782,14 +766,14 @@ async function repairProfile(force) {
|
||||
repairing.value = false
|
||||
|
||||
mixpanel_track('InstanceRepair', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
}
|
||||
|
||||
async function unpairProfile() {
|
||||
const editProfile = props.instance
|
||||
editProfile.metadata.linked_data = null
|
||||
editProfile.linked_data = null
|
||||
await edit(props.instance.path, editProfile)
|
||||
installedVersion.value = null
|
||||
installedVersionData.value = null
|
||||
@@ -798,13 +782,13 @@ async function unpairProfile() {
|
||||
|
||||
async function unlockProfile() {
|
||||
const editProfile = props.instance
|
||||
editProfile.metadata.linked_data.locked = false
|
||||
editProfile.linked_data.locked = false
|
||||
await edit(props.instance.path, editProfile)
|
||||
modalConfirmUnlock.value.hide()
|
||||
}
|
||||
|
||||
const isPackLocked = computed(() => {
|
||||
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked
|
||||
return props.instance.linked_data && props.instance.linked_data.locked
|
||||
})
|
||||
|
||||
async function repairModpack() {
|
||||
@@ -813,8 +797,8 @@ async function repairModpack() {
|
||||
inProgress.value = false
|
||||
|
||||
mixpanel_track('InstanceRepair', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -825,8 +809,8 @@ async function removeProfile() {
|
||||
removing.value = false
|
||||
|
||||
mixpanel_track('InstanceRemove', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
|
||||
await router.push({ path: '/' })
|
||||
@@ -843,10 +827,10 @@ const [
|
||||
all_game_versions,
|
||||
loaders,
|
||||
] = await Promise.all([
|
||||
get_fabric_versions().then(shallowRef).catch(handleError),
|
||||
get_forge_versions().then(shallowRef).catch(handleError),
|
||||
get_quilt_versions().then(shallowRef).catch(handleError),
|
||||
get_neoforge_versions().then(shallowRef).catch(handleError),
|
||||
get_loader_versions('fabric').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('forge').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('quilt').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('neo').then(shallowRef).catch(handleError),
|
||||
get_game_versions().then(shallowRef).catch(handleError),
|
||||
get_loaders()
|
||||
.then((value) =>
|
||||
@@ -859,8 +843,8 @@ const [
|
||||
])
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const loader = ref(props.instance.metadata.loader)
|
||||
const gameVersion = ref(props.instance.metadata.game_version)
|
||||
const loader = ref(props.instance.loader)
|
||||
const gameVersion = ref(props.instance.game_version)
|
||||
const selectableGameVersions = computed(() => {
|
||||
return all_game_versions.value
|
||||
.filter((item) => {
|
||||
@@ -896,9 +880,7 @@ const selectableLoaderVersions = computed(() => {
|
||||
return []
|
||||
})
|
||||
const loaderVersionIndex = ref(
|
||||
selectableLoaderVersions.value.findIndex(
|
||||
(x) => x.id === props.instance.metadata.loader_version?.id,
|
||||
),
|
||||
selectableLoaderVersions.value.findIndex((x) => x.id === props.instance.loader_version),
|
||||
)
|
||||
|
||||
const isValid = computed(() => {
|
||||
@@ -910,10 +892,9 @@ const isValid = computed(() => {
|
||||
|
||||
const isChanged = computed(() => {
|
||||
return (
|
||||
loader.value != props.instance.metadata.loader ||
|
||||
gameVersion.value != props.instance.metadata.game_version ||
|
||||
JSON.stringify(selectableLoaderVersions.value[loaderVersionIndex.value]) !==
|
||||
JSON.stringify(props.instance.metadata.loader_version)
|
||||
loader.value !== props.instance.loader ||
|
||||
gameVersion.value !== props.instance.game_version ||
|
||||
selectableLoaderVersions.value[loaderVersionIndex.value].id !== props.instance.loader_version
|
||||
)
|
||||
})
|
||||
|
||||
@@ -924,11 +905,11 @@ async function saveGvLoaderEdits() {
|
||||
editing.value = true
|
||||
|
||||
let editProfile = editProfileObject.value
|
||||
editProfile.metadata.loader = loader.value
|
||||
editProfile.metadata.game_version = gameVersion.value
|
||||
editProfile.loader = loader.value
|
||||
editProfile.game_version = gameVersion.value
|
||||
|
||||
if (loader.value !== 'vanilla') {
|
||||
editProfile.metadata.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value]
|
||||
editProfile.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value].id
|
||||
}
|
||||
await edit(props.instance.path, editProfile).catch(handleError)
|
||||
await repairProfile(false)
|
||||
|
||||
@@ -4,26 +4,17 @@
|
||||
<Card v-if="instance" class="small-instance">
|
||||
<router-link class="instance" :to="`/instance/${encodeURIComponent(instance.path)}`">
|
||||
<Avatar
|
||||
:src="
|
||||
!instance.metadata.icon ||
|
||||
(instance.metadata.icon && instance.metadata.icon.startsWith('http'))
|
||||
? instance.metadata.icon
|
||||
: convertFileSrc(instance.metadata?.icon)
|
||||
"
|
||||
:alt="instance.metadata.name"
|
||||
:src="instance.icon_path ? convertFileSrc(instance.icon_path) : null"
|
||||
:alt="instance.name"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="small-instance_info">
|
||||
<span class="title">{{
|
||||
instance.metadata.name.length > 20
|
||||
? instance.metadata.name.substring(0, 20) + '...'
|
||||
: instance.metadata.name
|
||||
instance.name.length > 20 ? instance.name.substring(0, 20) + '...' : instance.name
|
||||
}}</span>
|
||||
<span>
|
||||
{{
|
||||
instance.metadata.loader.charAt(0).toUpperCase() + instance.metadata.loader.slice(1)
|
||||
}}
|
||||
{{ instance.metadata.game_version }}
|
||||
{{ instance.loader.charAt(0).toUpperCase() + instance.loader.slice(1) }}
|
||||
{{ instance.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -209,7 +200,6 @@
|
||||
:project="data"
|
||||
:versions="versions"
|
||||
:members="members"
|
||||
:dependencies="dependencies"
|
||||
:instance="instance"
|
||||
:install="install"
|
||||
:installed="installed"
|
||||
@@ -218,9 +208,6 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
||||
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
|
||||
<template #install> <DownloadIcon /> Install </template>
|
||||
<template #open_link> <GlobeIcon /> Open in Modrinth <ExternalIcon /> </template>
|
||||
@@ -263,79 +250,63 @@ import {
|
||||
OpenCollectiveIcon,
|
||||
} from '@/assets/external'
|
||||
import { get_categories } from '@/helpers/tags'
|
||||
import { install as packInstall } from '@/helpers/pack'
|
||||
import {
|
||||
list,
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
get as getInstance,
|
||||
remove_project,
|
||||
} from '@/helpers/profile'
|
||||
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import { installVersionDependencies, isOffline } from '@/helpers/utils'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
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'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const incompatibilityWarning = ref(null)
|
||||
|
||||
const options = ref(null)
|
||||
const installing = ref(false)
|
||||
const data = shallowRef(null)
|
||||
const versions = shallowRef([])
|
||||
const members = shallowRef([])
|
||||
const dependencies = shallowRef([])
|
||||
const categories = shallowRef([])
|
||||
const instance = ref(null)
|
||||
const instanceProjects = ref(null)
|
||||
|
||||
const installed = ref(false)
|
||||
const installedVersion = ref(null)
|
||||
|
||||
const offline = ref(await isOffline())
|
||||
|
||||
async function fetchProjectData() {
|
||||
;[
|
||||
data.value,
|
||||
versions.value,
|
||||
members.value,
|
||||
dependencies.value,
|
||||
categories.value,
|
||||
instance.value,
|
||||
] = await Promise.all([
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'),
|
||||
get_categories().catch(handleError),
|
||||
route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(),
|
||||
])
|
||||
const project = await get_project(route.params.id).catch(handleError)
|
||||
|
||||
installed.value =
|
||||
instance.value?.path &&
|
||||
(await check_installed(instance.value.path, data.value.id).catch(handleError))
|
||||
data.value = project
|
||||
;[versions.value, members.value, categories.value, instance.value, instanceProjects.value] =
|
||||
await Promise.all([
|
||||
get_version_many(project.versions).catch(handleError),
|
||||
get_team(project.team).catch(handleError),
|
||||
get_categories().catch(handleError),
|
||||
route.query.i ? getInstance(route.query.i).catch(handleError) : Promise.resolve(),
|
||||
route.query.i ? getInstanceProjects(route.query.i).catch(handleError) : Promise.resolve(),
|
||||
])
|
||||
|
||||
versions.value = versions.value.sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
|
||||
|
||||
if (instanceProjects.value) {
|
||||
const installedFile = Object.values(instanceProjects.value).find(
|
||||
(x) => x.metadata && x.metadata.project_id === data.value.id,
|
||||
)
|
||||
if (installedFile) {
|
||||
installed.value = true
|
||||
installedVersion.value = installedFile.metadata.version_id
|
||||
}
|
||||
}
|
||||
breadcrumbs.setName('Project', data.value.title)
|
||||
installedVersion.value = instance.value
|
||||
? Object.values(instance.value.projects).find(
|
||||
(p) => p?.metadata?.version?.project_id === data.value.id,
|
||||
)?.metadata?.version?.id
|
||||
: null
|
||||
}
|
||||
|
||||
if (!offline.value) await fetchProjectData()
|
||||
await fetchProjectData()
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
@@ -346,162 +317,22 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const markInstalled = () => {
|
||||
installed.value = true
|
||||
}
|
||||
|
||||
async function install(version) {
|
||||
installing.value = true
|
||||
let queuedVersionData
|
||||
if (instance.value) {
|
||||
instance.value = await getInstance(instance.value.path, false).catch(handleError)
|
||||
}
|
||||
|
||||
if (installed.value) {
|
||||
const old_project = Object.entries(instance.value.projects)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))
|
||||
.find((p) => p.value.metadata?.version?.project_id === data.value.id)
|
||||
if (!old_project) {
|
||||
// Switching too fast, old project is not recognized as a Modrinth project yet
|
||||
await installVersion(
|
||||
data.value.id,
|
||||
version,
|
||||
instance.value ? instance.value.path : null,
|
||||
'ProjectPage',
|
||||
(version) => {
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
|
||||
await remove_project(instance.value.path, old_project.key)
|
||||
}
|
||||
|
||||
if (version) {
|
||||
queuedVersionData = versions.value.find((v) => v.id === version)
|
||||
} else {
|
||||
if (data.value.project_type === 'modpack' || !instance.value) {
|
||||
queuedVersionData = versions.value[0]
|
||||
} else {
|
||||
queuedVersionData = versions.value.find((v) =>
|
||||
v.game_versions.includes(data.value.game_versions[0]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.value.project_type === 'modpack') {
|
||||
const packs = Object.values(await list(true).catch(handleError))
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === data.value.id)
|
||||
) {
|
||||
await packInstall(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url,
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel_track('PackInstall', {
|
||||
id: data.value.id,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
confirmModal.value.show(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (instance.value) {
|
||||
if (!version) {
|
||||
const gameVersion = instance.value.metadata.game_version
|
||||
const loader = instance.value.metadata.loader
|
||||
const selectedVersion = versions.value.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(gameVersion) &&
|
||||
(data.value.project_type === 'mod'
|
||||
? v.loaders.includes(loader) || v.loaders.includes('minecraft')
|
||||
: true),
|
||||
)
|
||||
if (!selectedVersion) {
|
||||
incompatibilityWarning.value.show(
|
||||
instance.value,
|
||||
data.value.title,
|
||||
versions.value,
|
||||
markInstalled,
|
||||
data.value.id,
|
||||
data.value.project_type,
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
} else {
|
||||
queuedVersionData = selectedVersion
|
||||
await installMod(instance.value.path, selectedVersion.id).catch(handleError)
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
installedVersion.value = selectedVersion.id
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
id: data.value.id,
|
||||
project_type: data.value.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const gameVersion = instance.value.metadata.game_version
|
||||
const loader = instance.value.metadata.loader
|
||||
const compatible = versions.value.some(
|
||||
(v) =>
|
||||
v.game_versions.includes(gameVersion) &&
|
||||
(data.value.project_type === 'mod'
|
||||
? v.loaders.includes(loader) || v.loaders.includes('minecraft')
|
||||
: true),
|
||||
)
|
||||
if (compatible) {
|
||||
await installMod(instance.value.path, queuedVersionData.id).catch(handleError)
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
installedVersion.value = queuedVersionData.id
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
id: data.value.id,
|
||||
project_type: data.value.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
incompatibilityWarning.value.show(
|
||||
instance.value,
|
||||
data.value.title,
|
||||
[queuedVersionData],
|
||||
markInstalled,
|
||||
data.value.id,
|
||||
data.value.project_type,
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
if (instance.value && version) {
|
||||
installed.value = true
|
||||
installedVersion.value = version
|
||||
}
|
||||
installed.value = true
|
||||
} else {
|
||||
modInstallModal.value.show(
|
||||
data.value.id,
|
||||
version ? [versions.value.find((v) => v.id === queuedVersionData.id)] : versions.value,
|
||||
data.value.title,
|
||||
data.value.project_type,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
installing.value = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const handleRightClick = (e) => {
|
||||
|
||||
@@ -190,6 +190,7 @@ import { ref, watch, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { SwapIcon } from '@/assets/icons'
|
||||
import { get_project_many, get_version_many } from '@/helpers/cache.js'
|
||||
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -204,10 +205,6 @@ const props = defineProps({
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
dependencies: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
members: {
|
||||
type: Array,
|
||||
required: true,
|
||||
@@ -238,20 +235,45 @@ watch(
|
||||
async () => {
|
||||
if (route.params.version) {
|
||||
version.value = props.versions.find((version) => version.id === route.params.version)
|
||||
await refreshDisplayDependencies()
|
||||
breadcrumbs.setName('Version', version.value.name)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const author = computed(() =>
|
||||
props.members.find((member) => member.user.id === version.value.author_id),
|
||||
props.members ? props.members.find((member) => member.user.id === version.value.author_id) : null,
|
||||
)
|
||||
|
||||
const displayDependencies = computed(() =>
|
||||
version.value.dependencies.map((dependency) => {
|
||||
const version = props.dependencies.versions.find((obj) => obj.id === dependency.version_id)
|
||||
const displayDependencies = ref({})
|
||||
|
||||
async function refreshDisplayDependencies() {
|
||||
const projectIds = new Set()
|
||||
const versionIds = new Set()
|
||||
if (version.value.dependencies) {
|
||||
for (const dependency of version.value.dependencies) {
|
||||
if (dependency.project_id) {
|
||||
projectIds.add(dependency.project_id)
|
||||
}
|
||||
if (dependency.version_id) {
|
||||
versionIds.add(dependency.version_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
const [projectDeps, versionDeps] = await Promise.all([
|
||||
get_project_many([...projectIds]),
|
||||
get_version_many([...versionIds]),
|
||||
])
|
||||
|
||||
const dependencies = {
|
||||
projects: projectDeps,
|
||||
versions: versionDeps,
|
||||
}
|
||||
|
||||
displayDependencies.value = version.value.dependencies.map((dependency) => {
|
||||
const version = dependencies.versions.find((obj) => obj.id === dependency.version_id)
|
||||
if (version) {
|
||||
const project = props.dependencies.projects.find(
|
||||
const project = dependencies.projects.find(
|
||||
(obj) => obj.id === version.project_id || obj.id === dependency.project_id,
|
||||
)
|
||||
return {
|
||||
@@ -261,7 +283,7 @@ const displayDependencies = computed(() =>
|
||||
link: `/project/${project.slug}/version/${version.id}`,
|
||||
}
|
||||
} else {
|
||||
const project = props.dependencies.projects.find((obj) => obj.id === dependency.project_id)
|
||||
const project = dependencies.projects.find((obj) => obj.id === dependency.project_id)
|
||||
|
||||
if (project) {
|
||||
return {
|
||||
@@ -279,8 +301,9 @@ const displayDependencies = computed(() =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
await refreshDisplayDependencies()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -187,8 +187,8 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const filterVersions = ref([])
|
||||
const filterLoader = ref(props.instance ? [props.instance?.metadata?.loader] : [])
|
||||
const filterGameVersions = ref(props.instance ? [props.instance?.metadata?.game_version] : [])
|
||||
const filterLoader = ref(props.instance ? [props.instance?.loader] : [])
|
||||
const filterGameVersions = ref(props.instance ? [props.instance?.game_version] : [])
|
||||
|
||||
const currentPage = ref(1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user