You've already forked AstralRinth
forked from didirus/AstralRinth
Initial bug fixes (#127)
* Initial bug fixes * fix compile error on non-mac * Fix even more bugs * Fix more * fix more * fix build * fix build * address review comments
This commit is contained in:
@@ -53,6 +53,33 @@ const notificationsWrapper = ref(null)
|
||||
watch(notificationsWrapper, () => {
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
})
|
||||
|
||||
// Link handler
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
if (
|
||||
target.href &&
|
||||
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
||||
!target.classList.contains('router-link-active') &&
|
||||
!target.href.startsWith('http://localhost') &&
|
||||
!target.href.startsWith('https://tauri.localhost')
|
||||
) {
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: target.href,
|
||||
},
|
||||
})
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import 'inter.scss';
|
||||
|
||||
:root {
|
||||
font-family: var(--font-standard);
|
||||
color-scheme: dark;
|
||||
@@ -7,10 +5,15 @@
|
||||
--expanded-view-width: calc(100% - 13rem);
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
@@ -41,130 +44,7 @@ a {
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
color: var(--color-base) !important;
|
||||
outline: 2px solid transparent;
|
||||
max-width: 15rem;
|
||||
width: 100% !important;
|
||||
|
||||
.multiselect__input:focus-visible {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
font-weight: normal !important;
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-button-bg);
|
||||
box-shadow: var(--shadow-inset-sm);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding-left: 0.5rem;
|
||||
font-size: 1rem;
|
||||
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
|
||||
.multiselect__spinner {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-base);
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-brand);
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
background: transparent;
|
||||
|
||||
&:after {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
color: var(--color-base);
|
||||
margin-left: 0.5rem;
|
||||
opacity: 0.6;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
background: var(--color-button-bg);
|
||||
border: none;
|
||||
overflow-x: hidden;
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
width: 100%;
|
||||
|
||||
.multiselect__element {
|
||||
.multiselect__option--highlight {
|
||||
background: var(--color-button-bg);
|
||||
filter: brightness(1.25);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background: var(--color-brand);
|
||||
font-weight: bold;
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__spinner {
|
||||
background: var(--color-button-bg);
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
&.multiselect--disabled {
|
||||
background: none;
|
||||
|
||||
.multiselect__current,
|
||||
.multiselect__select {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-top: none !important;
|
||||
border-top-left-radius: var(--radius-md) !important;
|
||||
border-top-right-radius: var(--radius-md) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.mod-text {
|
||||
@@ -173,7 +53,3 @@ input {
|
||||
gap: 1rem;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
input {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// TODO: move to omorphia
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -9,19 +8,11 @@ const props = defineProps({
|
||||
return []
|
||||
},
|
||||
},
|
||||
news: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
canPaginate: Boolean,
|
||||
})
|
||||
const modsRow = ref(null)
|
||||
</script>
|
||||
<template>
|
||||
<div class="row">
|
||||
@@ -29,7 +20,7 @@ const modsRow = ref(null)
|
||||
<p>{{ props.label }}</p>
|
||||
<hr />
|
||||
</div>
|
||||
<section ref="modsRow" class="instances">
|
||||
<section class="instances">
|
||||
<Instance
|
||||
v-for="instance in props.instances"
|
||||
:key="instance.id"
|
||||
@@ -56,6 +47,7 @@ const modsRow = ref(null)
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
@@ -101,7 +93,6 @@ const modsRow = ref(null)
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-right: auto;
|
||||
margin-top: 0.8rem;
|
||||
scroll-behavior: smooth;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -42,16 +42,10 @@ onMounted(() => {
|
||||
|
||||
handlePaginationDisplay()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
||||
})
|
||||
|
||||
const handleLeftPage = () => {
|
||||
modsRow.value.scrollLeft -= 170
|
||||
}
|
||||
const handleRightPage = () => {
|
||||
modsRow.value.scrollLeft += 170
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="props.instances.length > 0" class="row">
|
||||
@@ -59,8 +53,8 @@ const handleRightPage = () => {
|
||||
<p>{{ props.label }}</p>
|
||||
<hr aria-hidden="true" />
|
||||
<div v-if="allowPagination" class="pagination">
|
||||
<ChevronLeftIcon role="button" @click="handleLeftPage" />
|
||||
<ChevronRightIcon role="button" @click="handleRightPage" />
|
||||
<ChevronLeftIcon role="button" @click="modsRow.value.scrollLeft -= 170" />
|
||||
<ChevronRightIcon role="button" @click="modsRow.value.scrollLeft += 170" />
|
||||
</div>
|
||||
</div>
|
||||
<section ref="modsRow" class="instances">
|
||||
@@ -95,6 +89,7 @@ const handleRightPage = () => {
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
@@ -140,11 +135,14 @@ const handleRightPage = () => {
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-right: auto;
|
||||
margin-top: 0.8rem;
|
||||
scroll-behavior: smooth;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
:deep(.instance-card-item) {
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
background: transparent;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="text no-select">
|
||||
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
||||
</div>
|
||||
<p class="no-select">
|
||||
<p class="accounts-text no-select">
|
||||
<UsersIcon />
|
||||
Accounts
|
||||
</p>
|
||||
@@ -24,13 +24,13 @@
|
||||
<h4>{{ selectedAccount.username }}</h4>
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="logged-out account">
|
||||
<h4>Not signed in</h4>
|
||||
<Button icon-only color="primary" @click="login()">
|
||||
<Button v-tooltip="'Log in'" icon-only color="primary" @click="login()">
|
||||
<LogInIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
<Avatar :src="account.profile_picture" class="icon" />
|
||||
<p>{{ account.username }}</p>
|
||||
</Button>
|
||||
<Button icon-only @click="logout(account.id)">
|
||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -79,7 +79,7 @@ const appendProfiles = (accounts) => {
|
||||
return accounts.map((account) => {
|
||||
return {
|
||||
...account,
|
||||
profile_picture: `https://crafthead.net/helm/${account.id.replace(/-/g, '')}/128`,
|
||||
profile_picture: `https://mc-heads.net/avatar/${account.id}/128`,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -187,6 +187,11 @@ onBeforeUnmount(() => {
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
@@ -287,4 +292,11 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accounts-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,13 +5,15 @@ import { ref } from 'vue'
|
||||
|
||||
const version = ref('')
|
||||
const title = ref('')
|
||||
const projectId = ref('')
|
||||
const icon = ref('')
|
||||
const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: (id, projectTitle, projectIcon) => {
|
||||
show: (id, projectId, projectTitle, projectIcon) => {
|
||||
version.value = id
|
||||
projectId.value = projectId
|
||||
title.value = projectTitle
|
||||
icon.value = projectIcon
|
||||
confirmModal.value.show()
|
||||
@@ -20,7 +22,7 @@ defineExpose({
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
await pack_install(version.value, title.value, icon.value ? icon.value : null)
|
||||
await pack_install(projectId.value, version.value, title.value, icon.value ? icon.value : null)
|
||||
confirmModal.value.hide()
|
||||
}
|
||||
</script>
|
||||
@@ -28,10 +30,8 @@ async function install() {
|
||||
<template>
|
||||
<Modal ref="confirmModal" header="Are you sure?">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This project is already installed on your system. Are you sure you want to install it again?
|
||||
</p>
|
||||
<div class="button-group">
|
||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||
<div class="input-group push-right">
|
||||
<Button @click="() => $refs.confirmModal.hide()"><XIcon />Cancel</Button>
|
||||
<Button color="primary" :disabled="installing" @click="install()"
|
||||
><DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}</Button
|
||||
@@ -48,11 +48,4 @@ async function install() {
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, useSlots } from 'vue'
|
||||
import { onUnmounted, ref, useSlots, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Card, DownloadIcon, XIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { run, list } from '@/helpers/profile'
|
||||
import {
|
||||
@@ -14,6 +13,8 @@ import {
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -29,10 +30,20 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const playing = ref(false)
|
||||
|
||||
const uuid = ref(null)
|
||||
const modLoading = ref(false)
|
||||
const modLoading = ref(
|
||||
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false
|
||||
)
|
||||
|
||||
watch(props.instance, () => {
|
||||
modLoading.value = props.instance.install_stage
|
||||
? props.instance.install_stage !== 'installed'
|
||||
: false
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -74,21 +85,26 @@ const install = async (e) => {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.instance.project_id)
|
||||
) {
|
||||
try {
|
||||
modLoading.value = true
|
||||
await pack_install(versions[0].id, props.instance.title, props.instance.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
modLoading.value = false
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
modLoading.value = false
|
||||
}
|
||||
} else confirmModal.value.show(versions[0].id, props.instance.title, props.instance.icon_url)
|
||||
modLoading.value = true
|
||||
await pack_install(
|
||||
props.instance.project_id,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
props.instance.icon_url
|
||||
).catch(handleError)
|
||||
modLoading.value = false
|
||||
} else
|
||||
confirmModal.value.show(
|
||||
props.instance.project_id,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
props.instance.icon_url
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(props.instance.project_id, versions)
|
||||
}
|
||||
|
||||
modLoading.value = false
|
||||
// TODO: Add condition for installing a mod
|
||||
}
|
||||
|
||||
const play = async (e) => {
|
||||
@@ -103,21 +119,14 @@ const stop = async (e) => {
|
||||
e.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
try {
|
||||
// If we lost the uuid for some reason, such as a user navigating
|
||||
// from-then-back to this page, we will get all uuids by the instance path.
|
||||
// For-each uuid, kill the process.
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
|
||||
uuid.value = uuids[0]
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
|
||||
} catch (err) {
|
||||
// Theseus currently throws:
|
||||
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error
|
||||
// For now, we will catch and just warn
|
||||
console.warn(err)
|
||||
}
|
||||
// If we lost the uuid for some reason, such as a user navigating
|
||||
// from-then-back to this page, we will get all uuids by the instance path.
|
||||
// For-each uuid, kill the process.
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
|
||||
uuid.value = uuids[0]
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
|
||||
|
||||
uuid.value = null
|
||||
}
|
||||
@@ -206,9 +215,10 @@ onUnmounted(() => unlisten())
|
||||
>
|
||||
<XIcon />
|
||||
</div>
|
||||
<div v-else class="install cta buttonbase" @click="install"><DownloadIcon /></div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
</template>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -276,7 +286,7 @@ onUnmounted(() => unlisten())
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: 4.5rem;
|
||||
bottom: 5.5rem;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
@@ -312,11 +322,12 @@ onUnmounted(() => unlisten())
|
||||
z-index: 1;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: 1rem;
|
||||
bottom: 3.5rem;
|
||||
right: 1.25rem;
|
||||
bottom: 5rem;
|
||||
opacity: 0;
|
||||
transition: 0.3s ease-in-out bottom, 0.1s ease-in-out opacity !important;
|
||||
transition: 0.2s ease-in-out bottom, 0.1s ease-in-out opacity, 0.1s ease-in-out filter !important;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-floating);
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
@@ -324,11 +335,6 @@ onUnmounted(() => unlisten())
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: none !important; /* overrides button-base class */
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
|
||||
&.install {
|
||||
background: var(--color-brand);
|
||||
display: flex;
|
||||
@@ -400,6 +406,8 @@ onUnmounted(() => unlisten())
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Modal ref="modal" header="Create instance">
|
||||
<div v-if="showContent" class="modal-body">
|
||||
<div class="modal-body">
|
||||
<div class="image-upload">
|
||||
<Avatar :src="display_icon" size="md" :rounded="true" />
|
||||
<div class="image-input">
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Name</p>
|
||||
<input v-model="profile_name" class="text-input" type="text" />
|
||||
<input v-model="profile_name" autocomplete="off" class="text-input" type="text" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
@@ -25,7 +25,16 @@
|
||||
<div class="input-row">
|
||||
<p class="input-label">Game version</p>
|
||||
<div class="versions">
|
||||
<DropdownSelect v-model="game_version" :options="game_versions" render-up />
|
||||
<multiselect
|
||||
v-model="game_version"
|
||||
class="selector"
|
||||
:options="game_versions"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select game version"
|
||||
open-direction="top"
|
||||
:show-labels="false"
|
||||
/>
|
||||
<Checkbox
|
||||
v-if="showAdvanced"
|
||||
v-model="showSnapshots"
|
||||
@@ -41,17 +50,21 @@
|
||||
<div v-if="showAdvanced && loader_version === 'other' && loader !== 'vanilla'">
|
||||
<div v-if="game_version" class="input-row">
|
||||
<p class="input-label">Select version</p>
|
||||
<DropdownSelect
|
||||
<multiselect
|
||||
v-model="specified_loader_version"
|
||||
class="selector"
|
||||
:options="selectable_versions"
|
||||
render-up
|
||||
:searchable="true"
|
||||
placeholder="Select loader version"
|
||||
open-direction="top"
|
||||
:show-labels="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="input-row">
|
||||
<p class="warning">Select a game version before you select a loader version</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="input-group push-right">
|
||||
<Button @click="toggle_advanced">
|
||||
<CodeIcon />
|
||||
{{ showAdvanced ? 'Hide advanced' : 'Show advanced' }}
|
||||
@@ -74,7 +87,6 @@ import {
|
||||
Avatar,
|
||||
Button,
|
||||
Chips,
|
||||
DropdownSelect,
|
||||
Modal,
|
||||
PlusIcon,
|
||||
UploadIcon,
|
||||
@@ -83,19 +95,24 @@ import {
|
||||
Checkbox,
|
||||
} from 'omorphia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata'
|
||||
import {
|
||||
get_game_versions,
|
||||
get_fabric_versions,
|
||||
get_forge_versions,
|
||||
get_quilt_versions,
|
||||
} from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
const loader = ref('vanilla')
|
||||
const loader_version = ref('stable')
|
||||
const specified_loader_version = ref('')
|
||||
const showContent = ref(false)
|
||||
const icon = ref(null)
|
||||
const display_icon = ref(null)
|
||||
const showAdvanced = ref(false)
|
||||
@@ -104,22 +121,17 @@ const showSnapshots = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
showContent.value = false
|
||||
modal.value.show()
|
||||
game_version.value = ''
|
||||
specified_loader_version.value = ''
|
||||
profile_name.value = ''
|
||||
creating.value = false
|
||||
showAdvanced.value = false
|
||||
showSnapshots.value = false
|
||||
loader.value = ''
|
||||
loader.value = 'vanilla'
|
||||
loader_version.value = 'stable'
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
|
||||
setTimeout(() => {
|
||||
showContent.value = true
|
||||
}, 100)
|
||||
modal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -138,53 +150,50 @@ const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loade
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
loaders.value.push('vanilla')
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const game_versions = computed(() => {
|
||||
return all_game_versions.value
|
||||
return all_game_versions.value.versions
|
||||
.filter((item) => {
|
||||
let defaultVal = item.version_type === 'release' || showSnapshots.value
|
||||
let defaultVal = item.type === 'release' || showSnapshots.value
|
||||
if (loader.value === 'fabric') {
|
||||
defaultVal &= fabric_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= fabric_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'forge') {
|
||||
defaultVal &= forge_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= forge_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'quilt') {
|
||||
defaultVal &= quilt_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= quilt_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
}
|
||||
|
||||
return defaultVal
|
||||
})
|
||||
.map((item) => item.version)
|
||||
.map((item) => item.id)
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return (
|
||||
profile_name.value && game_version.value && game_versions.value.includes(game_version.value)
|
||||
profile_name.value.trim() &&
|
||||
game_version.value &&
|
||||
game_versions.value.includes(game_version.value)
|
||||
)
|
||||
})
|
||||
|
||||
const create_instance = async () => {
|
||||
try {
|
||||
creating.value = true
|
||||
const loader_version_value =
|
||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||
creating.value = true
|
||||
const loader_version_value =
|
||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||
|
||||
await create(
|
||||
profile_name.value,
|
||||
game_version.value,
|
||||
loader.value,
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
create(
|
||||
profile_name.value,
|
||||
game_version.value,
|
||||
loader.value,
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
|
||||
modal.value.hide()
|
||||
creating.value = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
creating.value = false
|
||||
}
|
||||
modal.value.hide()
|
||||
creating.value = false
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
@@ -246,12 +255,6 @@ const toggle_advanced = () => {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.image-upload {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
@@ -277,4 +280,8 @@ const toggle_advanced = () => {
|
||||
:deep(button.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.selector {
|
||||
max-width: 20rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -143,9 +143,15 @@ const check_valid = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="installModal" header="Install mod to instance">
|
||||
<Modal ref="installModal" header="Install project to instance">
|
||||
<div class="modal-body">
|
||||
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="search"
|
||||
placeholder="Search for an instance"
|
||||
/>
|
||||
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
||||
<Button
|
||||
@@ -181,7 +187,13 @@ const check_valid = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-settings">
|
||||
<input v-model="name" type="text" placeholder="Name" class="creation-input" />
|
||||
<input
|
||||
v-model="name"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
class="creation-input"
|
||||
/>
|
||||
<Button :disabled="creatingInstance === true || !check_valid" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
@@ -189,7 +201,7 @@ const check_valid = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="footer">
|
||||
<div class="input-group push-right">
|
||||
<Button :color="showCreation ? '' : 'primary'" @click="toggleCreation()">
|
||||
<PlusIcon />
|
||||
{{ showCreation ? 'Hide New Instance' : 'Create new instance' }}
|
||||
@@ -302,12 +314,4 @@ const check_valid = computed(() => {
|
||||
.profile-image {
|
||||
--size: 2rem !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="table-cell table-text">
|
||||
<span>{{ javaInstall.version }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text">
|
||||
<div v-tooltip="javaInstall.path" class="table-cell table-text">
|
||||
<span>{{ javaInstall.path }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text manage">
|
||||
@@ -22,10 +22,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
|
||||
<div class="table-cell table-text">No JARS Found!</div>
|
||||
<div class="table-cell table-text">No java installations found!</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="input-group push-right">
|
||||
<Button @click="$refs.detectJavaModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
@@ -96,13 +96,6 @@ function setJavaInstall(javaInstall) {
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.manage {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
|
||||
<div class="toggle-setting">
|
||||
<input
|
||||
autocomplete="off"
|
||||
:disabled="props.disabled"
|
||||
:value="props.modelValue ? props.modelValue.path : ''"
|
||||
type="text"
|
||||
@@ -28,30 +29,25 @@
|
||||
<FolderSearchIcon />
|
||||
Browse
|
||||
</Button>
|
||||
<Button :disabled="props.disabled" @click="testJava">
|
||||
<Button v-if="testingJava" disabled> Testing... </Button>
|
||||
<Button v-else-if="testingJavaSuccess === true">
|
||||
<CheckIcon class="test-success" />
|
||||
Success
|
||||
</Button>
|
||||
<Button v-else-if="testingJavaSuccess === false">
|
||||
<XIcon class="test-fail" />
|
||||
Failed
|
||||
</Button>
|
||||
<Button v-else :disabled="props.disabled" @click="testJava">
|
||||
<PlayIcon />
|
||||
Test
|
||||
</Button>
|
||||
<AnimatedLogo v-if="testingJava === true" class="testing-loader" />
|
||||
<CheckIcon
|
||||
v-else-if="testingJavaSuccess === true && testingJava === false"
|
||||
class="test-success"
|
||||
/>
|
||||
<XIcon v-else-if="testingJavaSuccess === false && testingJava === false" class="test-fail" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
SearchIcon,
|
||||
PlayIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
AnimatedLogo,
|
||||
FolderSearchIcon,
|
||||
} from 'omorphia'
|
||||
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
|
||||
import { get_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
@@ -143,14 +139,3 @@ async function handleJavaFileInput() {
|
||||
color: var(--color-red);
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.testing-loader {
|
||||
height: 1rem !important;
|
||||
width: 1rem !important;
|
||||
|
||||
svg {
|
||||
height: inherit !important;
|
||||
width: inherit !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<span class="running-text">
|
||||
{{ currentProcesses[0].metadata.name }}
|
||||
</span>
|
||||
<Button icon-only class="icon-button stop" @click="stop()">
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop()">
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button icon-only class="icon-button" @click="goToTerminal()">
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<div v-else class="status">
|
||||
<span class="circle stopped" />
|
||||
<span class="running-text"> No running profiles </span>
|
||||
<span class="running-text"> No running instances </span>
|
||||
<Button
|
||||
v-if="currentLoadingBars.length > 0"
|
||||
ref="infoButton"
|
||||
|
||||
@@ -140,7 +140,7 @@ async function install() {
|
||||
queuedVersionData = versions.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(props.instance.metadata.game_version) &&
|
||||
v.loaders.includes(props.instance.metadata.loader)
|
||||
(props.project.project_type !== 'mod' || v.loaders.includes(props.instance.metadata.loader))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,11 +152,19 @@ async function install() {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
await packInstall(queuedVersionData.id, props.project.title, props.project.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
await packInstall(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
).catch(handleError)
|
||||
} else {
|
||||
props.confirmModal.show(queuedVersionData.id)
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (props.instance) {
|
||||
|
||||
@@ -35,6 +35,8 @@ defineProps({
|
||||
svg {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
fill: var(--color-brand);
|
||||
color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,6 +28,10 @@ export async function authenticate_await_completion() {
|
||||
return await invoke('auth_authenticate_await_completion')
|
||||
}
|
||||
|
||||
export async function cancel_flow() {
|
||||
return await invoke('auth_cancel_flow')
|
||||
}
|
||||
|
||||
/// Refresh some credentials using Hydra, if needed
|
||||
/// user is UUID
|
||||
/// update_name is bool
|
||||
|
||||
@@ -6,5 +6,6 @@ export const useFetch = async (url, item) => {
|
||||
return await ofetch(url)
|
||||
} catch (err) {
|
||||
handleError({ message: `Error fetching ${item}` })
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Installs pack from a version ID
|
||||
export async function install(versionId, packTitle, packIcon) {
|
||||
return await invoke('pack_install_version_id', { versionId, packTitle, packIcon })
|
||||
export async function install(projectId, versionId, packTitle, packIcon) {
|
||||
return await invoke('pack_install_version_id', { projectId, versionId, packTitle, packIcon })
|
||||
}
|
||||
|
||||
// Installs pack from a path
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { add_project_from_version as installMod, check_installed } from '@/helpers/profile'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function showInFolder(path) {
|
||||
return await invoke('show_in_folder', { path })
|
||||
}
|
||||
|
||||
export const releaseColor = (releaseType) => {
|
||||
switch (releaseType) {
|
||||
@@ -17,11 +22,20 @@ export const releaseColor = (releaseType) => {
|
||||
|
||||
export const installVersionDependencies = async (profile, version) => {
|
||||
for (const dep of version.dependencies) {
|
||||
if (dep.dependency_type !== 'required') continue
|
||||
if (dep.version_id) {
|
||||
if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
await installMod(profile.path, dep.version_id)
|
||||
} else {
|
||||
if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
const depVersions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${dep.project_id}/version`,
|
||||
'dependency versions'
|
||||
|
||||
@@ -4,6 +4,7 @@ import App from '@/App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'omorphia/dist/style.css'
|
||||
import '@/assets/stylesheets/global.scss'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import { initialize_state } from '@/helpers/state'
|
||||
import loadCssMixin from './mixins/macCssFix.js'
|
||||
|
||||
@@ -150,6 +150,25 @@ const handleInstanceSwitch = async (value) => {
|
||||
searchStore.ignoreInstance = value
|
||||
await switchPage(1)
|
||||
}
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const values = [
|
||||
{ label: 'Data Packs', href: `/browse/datapack` },
|
||||
{ label: 'Shaders', href: `/browse/shader` },
|
||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||
]
|
||||
|
||||
if (searchStore.instanceContext) {
|
||||
if (searchStore.instanceContext.metadata.loader !== 'vanilla') {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
}
|
||||
} else {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
||||
}
|
||||
|
||||
return values
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -160,7 +179,7 @@ const handleInstanceSwitch = async (value) => {
|
||||
<Checkbox
|
||||
:model-value="searchStore.ignoreInstance"
|
||||
:checked="searchStore.ignoreInstance"
|
||||
label="Unfilter loader & version"
|
||||
label="Show unsupported content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||
/>
|
||||
@@ -277,30 +296,14 @@ const handleInstanceSwitch = async (value) => {
|
||||
<div class="search">
|
||||
<Promotion class="promotion" />
|
||||
<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' },
|
||||
]
|
||||
"
|
||||
/>
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
</Card>
|
||||
<Card class="search-panel-container">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="searchStore.searchInput"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
|
||||
@@ -20,18 +20,20 @@ breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
||||
const recentInstances = shallowRef([])
|
||||
|
||||
const getInstances = async () => {
|
||||
filter.value = ''
|
||||
const profiles = await list(true).catch(handleError)
|
||||
recentInstances.value = Object.values(profiles)
|
||||
|
||||
const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id)
|
||||
excludeIds.forEach((id, index) => {
|
||||
filter.value += `NOT"project_id"="${id}"`
|
||||
if (index < excludeIds.length - 1) filter.value += ' AND '
|
||||
})
|
||||
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}"`)
|
||||
}
|
||||
}
|
||||
filter.value = filters.join(' AND ')
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
console.log(filter.value)
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'featured modpacks'
|
||||
@@ -40,7 +42,7 @@ const getFeaturedModpacks = async () => {
|
||||
}
|
||||
const getFeaturedMods = async () => {
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows',
|
||||
'featured mods'
|
||||
)
|
||||
featuredMods.value = response.hits
|
||||
|
||||
@@ -53,15 +53,20 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<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>
|
||||
<div class="settings-page">
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Display</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme">
|
||||
<span class="label__title">Color theme</span>
|
||||
<span class="label__description">Change the global launcher color theme.</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
id="theme"
|
||||
name="Theme dropdown"
|
||||
:options="themeStore.themeOptions"
|
||||
:default-value="settings.theme"
|
||||
@@ -75,12 +80,15 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
<div class="description">
|
||||
<h3>Collapsed navigation mode</h3>
|
||||
<p>Change the style of the side navigation bar</p>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="collapsed-nav">
|
||||
<span class="label__title">Collapsed navigation mode</span>
|
||||
<span class="label__description"
|
||||
>Change the style of the side navigation bar to a compact version.</span
|
||||
>
|
||||
</label>
|
||||
<Toggle
|
||||
id="collapsed-nav"
|
||||
:model-value="themeStore.collapsedNavigation"
|
||||
:checked="themeStore.collapsedNavigation"
|
||||
@update:model-value="
|
||||
@@ -92,116 +100,187 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
<span>Maximum concurrent writes</span>
|
||||
<Slider
|
||||
v-model="settings.max_concurrent_writes"
|
||||
class="concurrent-downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Resource management</span>
|
||||
</h3>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Java</h2>
|
||||
<div class="settings-group">
|
||||
<h3>Java 17 location</h3>
|
||||
<JavaSelector v-model="settings.java_globals.JAVA_17" :version="17" />
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Java 8 location</h3>
|
||||
<JavaSelector v-model="settings.java_globals.JAVA_8" :version="8" />
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Java arguments</h3>
|
||||
<input
|
||||
v-model="settings.javaArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
|
||||
<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
|
||||
>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-downloads"
|
||||
v-model="settings.max_concurrent_downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Environment variables</h3>
|
||||
<input
|
||||
v-model="settings.envArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="Enter environment variables..."
|
||||
|
||||
<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
|
||||
>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-writes"
|
||||
v-model="settings.max_concurrent_writes"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Java settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<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" />
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Minimum memory
|
||||
<Slider v-model="settings.memory.minimum" :min="256" :max="maxMemory" :step="10" />
|
||||
<label for="java-args">
|
||||
<span class="label__title">Java arguments</span>
|
||||
</label>
|
||||
<input
|
||||
id="java-args"
|
||||
v-model="settings.javaArgs"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
/>
|
||||
<label for="env-vars">
|
||||
<span class="label__title">Environmental variables</span>
|
||||
</label>
|
||||
<input
|
||||
id="env-vars"
|
||||
v-model="settings.envArgs"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter environmental variables..."
|
||||
/>
|
||||
<hr class="card-divider" />
|
||||
<div class="adjacent-input">
|
||||
<label for="max-memory">
|
||||
<span class="label__title">Java memory</span>
|
||||
<span class="label__description">
|
||||
The memory allocated to each instance when it is ran.
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum memory
|
||||
<Slider v-model="settings.memory.maximum" :min="256" :max="maxMemory" :step="10" />
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-memory"
|
||||
v-model="settings.memory.maximum"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Hooks</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 exit
|
||||
<input v-model="settings.hooks.post_exit" type="text" class="input" />
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Hooks</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="pre-launch">
|
||||
<span class="label__title">Pre launch</span>
|
||||
<span class="label__description"> Ran before the instance is launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="pre-launch"
|
||||
v-model="settings.hooks.pre_launch"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter pre-launch command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="wrapper">
|
||||
<span class="label__title">Wrapper</span>
|
||||
<span class="label__description"> Wrapper command for launching Minecraft. </span>
|
||||
</label>
|
||||
<input
|
||||
id="wrapper"
|
||||
v-model="settings.hooks.wrapper"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter wrapper command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="post-exit">
|
||||
<span class="label__title">Post exit</span>
|
||||
<span class="label__description"> Ran after the game closes. </span>
|
||||
</label>
|
||||
<input
|
||||
id="post-exit"
|
||||
v-model="settings.hooks.post_exit"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter post-exit command..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Window Size</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Width
|
||||
<input v-model="settings.game_resolution[0]" type="number" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Height
|
||||
<input v-model="settings.game_resolution[1]" type="number" class="input" />
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Window size</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="width">
|
||||
<span class="label__title">Width</span>
|
||||
<span class="label__description"> The width of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="width"
|
||||
v-model="settings.game_resolution[0]"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
placeholder="Enter width..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="height">
|
||||
<span class="label__title">Height</span>
|
||||
<span class="label__description"> The height of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="height"
|
||||
v-model="settings.game_resolution[1]"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="Enter height..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.concurrent-downloads {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
.slider-input {
|
||||
width: 5rem !important;
|
||||
flex-basis: 5rem !important;
|
||||
.settings-page {
|
||||
margin: 1rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.installation-input {
|
||||
@@ -209,54 +288,11 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.theming,
|
||||
.settings-card {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.theming {
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.card-divider {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,9 +48,13 @@
|
||||
>
|
||||
Loading...
|
||||
</Button>
|
||||
<!--TODO: https://github.com/tauri-apps/tauri/issues/4062 -->
|
||||
<Button class="instance-button" icon-only @click="open({ defaultPath: instance.path })">
|
||||
<Button
|
||||
v-tooltip="'Open instance folder'"
|
||||
class="instance-button"
|
||||
@click="showInFolder(instance.path)"
|
||||
>
|
||||
<FolderOpenIcon />
|
||||
Folder
|
||||
</Button>
|
||||
</span>
|
||||
</Card>
|
||||
@@ -104,8 +108,8 @@ import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { handleError, useBreadcrumbs, useLoading, useSearch } from '@/store/state'
|
||||
import { showInFolder } from '@/helpers/utils.js'
|
||||
|
||||
const route = useRoute()
|
||||
const searchStore = useSearch()
|
||||
@@ -148,24 +152,17 @@ await checkProcess()
|
||||
|
||||
const stopInstance = async () => {
|
||||
playing.value = false
|
||||
|
||||
try {
|
||||
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)
|
||||
} catch (err) {
|
||||
// Theseus currently throws:
|
||||
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error
|
||||
// For now, we will catch and just warn
|
||||
console.warn(err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
searchStore.instanceContext = instance.value
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div ref="logContainer" class="log-text">
|
||||
<!-- {{ logs[1] }}-->
|
||||
<div v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
{{ line }}
|
||||
</div>
|
||||
<span v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
{{ line }} <br />
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -210,5 +209,9 @@ onUnmounted(() => {
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
color-scheme: dark;
|
||||
|
||||
.no-wrap {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,29 +3,55 @@
|
||||
<div class="card-row">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon />
|
||||
<input v-model="searchFilter" type="text" placeholder="Search Mods" class="text-input" />
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
type="text"
|
||||
:placeholder="`Search ${search.length} ${(['All', 'Other'].includes(selectedProjectType)
|
||||
? 'projects'
|
||||
: selectedProjectType.toLowerCase()
|
||||
).slice(0, search.length === 1 ? -1 : 64)}...`"
|
||||
class="text-input"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<span class="manage">
|
||||
<span class="text-combo">
|
||||
<span class="no-wrap sort"> Sort By </span>
|
||||
<span class="no-wrap sort"> Sort by </span>
|
||||
<DropdownSelect
|
||||
v-model="sortFilter"
|
||||
name="sort-by"
|
||||
:options="['Name', 'Version', 'Author']"
|
||||
:options="['Name', 'Version', 'Author', 'Enabled']"
|
||||
default-value="Name"
|
||||
class="dropdown"
|
||||
/>
|
||||
</span>
|
||||
<Button color="primary" @click="router.push({ path: '/browse/mod' })">
|
||||
<Button
|
||||
color="primary"
|
||||
@click="
|
||||
router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
})
|
||||
"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span class="no-wrap"> Add Content </span>
|
||||
<span class="no-wrap"> Add content </span>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<Chips
|
||||
v-if="Object.keys(selectableProjectTypes).length > 1"
|
||||
v-model="selectedProjectType"
|
||||
:items="Object.keys(selectableProjectTypes)"
|
||||
/>
|
||||
<div class="table">
|
||||
<div class="table-row table-head">
|
||||
<div class="table-cell table-text">
|
||||
<Button icon-only :disabled="!projects.some((x) => x.outdated)" @click="updateAll">
|
||||
<Button
|
||||
v-tooltip="'Update all projects'"
|
||||
icon-only
|
||||
:disabled="!projects.some((x) => x.outdated)"
|
||||
@click="updateAll"
|
||||
>
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -37,7 +63,13 @@
|
||||
<div v-for="mod in search" :key="mod.file_name" class="table-row">
|
||||
<div class="table-cell table-text">
|
||||
<AnimatedLogo v-if="mod.updating" class="btn icon-only updating-indicator"></AnimatedLogo>
|
||||
<Button v-else :disabled="!mod.outdated" icon-only @click="updateProject(mod)">
|
||||
<Button
|
||||
v-else
|
||||
v-tooltip="'Update project'"
|
||||
:disabled="!mod.outdated"
|
||||
icon-only
|
||||
@click="updateProject(mod)"
|
||||
>
|
||||
<UpdatedIcon v-if="mod.outdated" />
|
||||
<CheckIcon v-else />
|
||||
</Button>
|
||||
@@ -55,11 +87,12 @@
|
||||
<div class="table-cell table-text">{{ mod.version }}</div>
|
||||
<div class="table-cell table-text">{{ mod.author }}</div>
|
||||
<div class="table-cell table-text manage">
|
||||
<Button icon-only @click="removeMod(mod)">
|
||||
<Button v-tooltip="'Remove project'" icon-only @click="removeMod(mod)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<input
|
||||
id="switch-1"
|
||||
autocomplete="off"
|
||||
type="checkbox"
|
||||
class="switch stylized-toggle"
|
||||
:checked="!mod.disabled"
|
||||
@@ -82,6 +115,8 @@ import {
|
||||
UpdatedIcon,
|
||||
DropdownSelect,
|
||||
AnimatedLogo,
|
||||
Chips,
|
||||
formatProjectType,
|
||||
} from 'omorphia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
@@ -120,6 +155,7 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
disabled: project.disabled,
|
||||
updateVersion: project.metadata.update_version,
|
||||
outdated: !!project.metadata.update_version,
|
||||
project_type: project.metadata.project.project_type,
|
||||
})
|
||||
} else if (project.metadata.type === 'inferred') {
|
||||
projects.value.push({
|
||||
@@ -131,6 +167,7 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
icon: project.metadata.icon ? convertFileSrc(project.metadata.icon) : null,
|
||||
disabled: project.disabled,
|
||||
outdated: false,
|
||||
project_type: project.metadata.project_type,
|
||||
})
|
||||
} else {
|
||||
projects.value.push({
|
||||
@@ -142,16 +179,33 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
icon: null,
|
||||
disabled: project.disabled,
|
||||
outdated: false,
|
||||
project_type: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const searchFilter = ref('')
|
||||
const sortFilter = ref('')
|
||||
const selectedProjectType = ref('All')
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const obj = { All: 'all' }
|
||||
|
||||
for (const project of projects.value) {
|
||||
obj[project.project_type ? formatProjectType(project.project_type) + 's' : 'Other'] =
|
||||
project.project_type
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
const search = computed(() => {
|
||||
const projectType = selectableProjectTypes.value[selectedProjectType.value]
|
||||
const filtered = projects.value.filter((mod) => {
|
||||
return mod.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
return (
|
||||
mod.name.toLowerCase().includes(searchFilter.value.toLowerCase()) &&
|
||||
(projectType === 'all' || mod.project_type === projectType)
|
||||
)
|
||||
})
|
||||
|
||||
return updateSort(filtered, sortFilter.value)
|
||||
@@ -179,6 +233,16 @@ function updateSort(projects, sort) {
|
||||
}
|
||||
return 0
|
||||
})
|
||||
case 'Enabled':
|
||||
return projects.slice().sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) {
|
||||
return 1
|
||||
}
|
||||
if (!a.disabled && b.disabled) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
default:
|
||||
return projects.slice().sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
@@ -230,6 +294,8 @@ async function updateProject(mod) {
|
||||
|
||||
async function toggleDisableMod(mod) {
|
||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||
console.log(mod.disabled)
|
||||
mod.disabled = !mod.disabled
|
||||
}
|
||||
|
||||
async function removeMod(mod) {
|
||||
@@ -248,6 +314,10 @@ async function removeMod(mod) {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
grid-template-columns: min-content 2fr 1fr 1fr 8rem;
|
||||
}
|
||||
@@ -284,14 +354,8 @@ async function removeMod(mod) {
|
||||
width: 7rem !important;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.sort {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.sort {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@@ -300,4 +364,8 @@ async function removeMod(mod) {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.v-popper--theme-tooltip .v-popper__inner {
|
||||
background: #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
<Chips v-model="loader" :items="loaders" />
|
||||
<Chips v-model="loader" :items="loaders" :never-empty="false" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Game Version</p>
|
||||
@@ -22,7 +22,7 @@
|
||||
@change="(value) => (loaderVersionIndex = value.index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="push-right input-group">
|
||||
<button class="btn" @click="$refs.changeVersionsModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
@@ -41,7 +41,7 @@
|
||||
<section class="card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Profile</span>
|
||||
<span class="label__title size-card-header">Instance</span>
|
||||
</h3>
|
||||
</div>
|
||||
<label for="instance-icon">
|
||||
@@ -68,13 +68,13 @@
|
||||
<label for="project-name">
|
||||
<span class="label__title">Name</span>
|
||||
</label>
|
||||
<input id="profile-name" v-model="title" maxlength="80" type="text" />
|
||||
<input id="profile-name" v-model="title" autocomplete="off" maxlength="80" type="text" />
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="edit-versions">
|
||||
<span class="label__title">Edit mod loader/game versions</span>
|
||||
<span class="label__description">
|
||||
Allows you to change the mod loader, loader version, or game version of the profile.
|
||||
Allows you to change the mod loader, loader version, or game version of the instance.
|
||||
</span>
|
||||
</label>
|
||||
<button id="edit-versions" class="btn" @click="$refs.changeVersionsModal.show()">
|
||||
@@ -83,8 +83,12 @@
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Java</h2>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Java</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Installation</h3>
|
||||
<Checkbox v-model="overrideJavaInstall" label="Override global java installations" />
|
||||
@@ -95,10 +99,12 @@
|
||||
<h3>Java arguments</h3>
|
||||
<Checkbox v-model="overrideJavaArgs" label="Override global java arguments" />
|
||||
<input
|
||||
id="java-args"
|
||||
v-model="javaArgs"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideJavaArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
class="installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
/>
|
||||
</div>
|
||||
@@ -107,98 +113,156 @@
|
||||
<Checkbox v-model="overrideEnvVars" label="Override global environment variables" />
|
||||
<input
|
||||
v-model="envVars"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideEnvVars"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
class="installation-input"
|
||||
placeholder="Enter environment variables..."
|
||||
/>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Java memory</h3>
|
||||
<Checkbox v-model="overrideMemorySettings" label="Override global memory settings" />
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Minimum memory
|
||||
<Slider
|
||||
v-model="memory.minimum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="10"
|
||||
/>
|
||||
<Slider
|
||||
v-model="memory.maximum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Window</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<Checkbox v-model="overrideWindowSettings" label="Override global window settings" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="width">
|
||||
<span class="label__title">Width</span>
|
||||
<span class="label__description"> The width of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="width"
|
||||
v-model="resolution[0]"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
placeholder="Enter width..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="height">
|
||||
<span class="label__title">Height</span>
|
||||
<span class="label__description"> The height of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="height"
|
||||
v-model="resolution[1]"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="Enter height..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Hooks</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<Checkbox v-model="overrideHooks" label="Override global hooks" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="pre-launch">
|
||||
<span class="label__title">Pre launch</span>
|
||||
<span class="label__description"> Ran before the instance is launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="pre-launch"
|
||||
v-model="hooks.pre_launch"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter pre-launch command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="wrapper">
|
||||
<span class="label__title">Wrapper</span>
|
||||
<span class="label__description"> Wrapper command for launching Minecraft. </span>
|
||||
</label>
|
||||
<input
|
||||
id="wrapper"
|
||||
v-model="hooks.wrapper"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter wrapper command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="post-exit">
|
||||
<span class="label__title">Post exit</span>
|
||||
<span class="label__description"> Ran after the game closes. </span>
|
||||
</label>
|
||||
<input
|
||||
id="post-exit"
|
||||
v-model="hooks.post_exit"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter post-exit command..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Instance management</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="repair-profile">
|
||||
<span class="label__title">Repair instance</span>
|
||||
<span class="label__description">
|
||||
Reinstalls the instance and checks for corruption. Use this if your game is not launching
|
||||
due to launcher-related errors.
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum memory
|
||||
<Slider
|
||||
v-model="memory.maximum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="10"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
id="repair-profile"
|
||||
class="btn btn-highlight"
|
||||
:disabled="repairing"
|
||||
@click="repairProfile"
|
||||
>
|
||||
<HammerIcon /> Repair
|
||||
</button>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="delete-profile">
|
||||
<span class="label__title">Delete instance</span>
|
||||
<span class="label__description">
|
||||
Fully removes a instance from the disk. Be careful, as once you delete a instance there is
|
||||
no way to recover it.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Window</h2>
|
||||
<Checkbox v-model="overrideWindowSettings" label="Override global window settings" />
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Width
|
||||
<input
|
||||
v-model="resolution[0]"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
@change="updateProfile"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Height
|
||||
<input
|
||||
v-model="resolution[1]"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
@change="updateProfile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Hooks</h2>
|
||||
<Checkbox v-model="overrideHooks" label="Override global hooks" />
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Pre launch
|
||||
<input v-model="hooks.pre_launch" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Wrapper
|
||||
<input v-model="hooks.wrapper" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Post exit
|
||||
<input v-model="hooks.post_exit" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Profile management</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Repair profile
|
||||
<button class="btn btn-highlight" :disabled="repairing" @click="repairProfile">
|
||||
<HammerIcon /> Repair
|
||||
</button>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Delete profile
|
||||
<button class="btn btn-danger" :disabled="removing" @click="removeProfile">
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<button
|
||||
id="delete-profile"
|
||||
class="btn btn-danger"
|
||||
:disabled="removing"
|
||||
@click="removeProfile"
|
||||
>
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -221,7 +285,7 @@ import {
|
||||
} from 'omorphia'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { edit, edit_icon, get_optimal_jre_key, install, remove } from '@/helpers/profile.js'
|
||||
import { computed, readonly, ref, shallowRef, watch } from 'vue'
|
||||
import { computed, onMounted, readonly, ref, shallowRef, watch } from 'vue'
|
||||
import { get_max_memory } from '@/helpers/jre.js'
|
||||
import { get } from '@/helpers/settings.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
@@ -388,10 +452,9 @@ const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loade
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
loaders.value.push('vanilla')
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const loader = ref(props.instance.metadata.loader)
|
||||
|
||||
const gameVersion = ref(props.instance.metadata.game_version)
|
||||
const selectableGameVersions = computed(() => {
|
||||
return all_game_versions.value
|
||||
@@ -457,6 +520,8 @@ async function saveGvLoaderEdits() {
|
||||
editing.value = false
|
||||
changeVersionsModal.value.hide()
|
||||
}
|
||||
|
||||
onMounted(() => console.log(loader.value))
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -478,73 +543,23 @@ async function saveGvLoaderEdits() {
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.installation-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
background-color: var(--color-button-bg);
|
||||
border: none;
|
||||
color: var(--color-button-bg);
|
||||
height: 1px;
|
||||
margin: var(--gap-sm) 0;
|
||||
}
|
||||
|
||||
:deep(button.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
</Button>
|
||||
<a
|
||||
class="open btn icon-only"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/${data.project_type}/${data.slug}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="btn"
|
||||
>
|
||||
<ExternalIcon />
|
||||
@@ -97,7 +96,6 @@
|
||||
:href="data.issues_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
<span>Issues</span>
|
||||
@@ -106,7 +104,6 @@
|
||||
v-if="data.source_url"
|
||||
:href="data.source_url"
|
||||
class="title"
|
||||
target="_blank"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
<CodeIcon aria-hidden="true" />
|
||||
@@ -117,7 +114,6 @@
|
||||
:href="data.wiki_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<WikiIcon aria-hidden="true" />
|
||||
<span>Wiki</span>
|
||||
@@ -127,7 +123,6 @@
|
||||
:href="data.wiki_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<DiscordIcon aria-hidden="true" />
|
||||
<span>Discord</span>
|
||||
@@ -136,7 +131,6 @@
|
||||
v-for="(donation, index) in data.donation_urls"
|
||||
:key="index"
|
||||
:href="donation.url"
|
||||
target="_blank"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
<BuyMeACoffeeIcon v-if="donation.id === 'bmac'" aria-hidden="true" />
|
||||
@@ -322,11 +316,19 @@ async function install(version) {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === data.value.id)
|
||||
) {
|
||||
await packInstall(queuedVersionData.id, data.value.title, data.value.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
await packInstall(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url
|
||||
).catch(handleError)
|
||||
} else {
|
||||
confirmModal.value.show(queuedVersionData.id, data.value.title, data.value.icon_url)
|
||||
confirmModal.value.show(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (instance.value) {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="btn"
|
||||
>
|
||||
<ExternalIcon />
|
||||
@@ -145,7 +144,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/user/${author.user.username}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="metadata-value btn author"
|
||||
>
|
||||
<Avatar size="sm" :src="author.user.avatar_url" circle />
|
||||
|
||||
@@ -105,6 +105,15 @@ export default new createRouter({
|
||||
breadcrumb: [{ name: '?Instance' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:type',
|
||||
name: 'ModsFilter',
|
||||
component: Instance.Mods,
|
||||
meta: {
|
||||
useRootContext: true,
|
||||
breadcrumb: [{ name: '?Instance' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'options',
|
||||
name: 'Options',
|
||||
@@ -128,4 +137,8 @@ export default new createRouter({
|
||||
],
|
||||
linkActiveClass: 'router-link-active',
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
scrollBehavior() {
|
||||
// always scroll to top
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user