You've already forked AstralRinth
forked from didirus/AstralRinth
Non modpack wireup & Project to profile install (#90)
* Base impl * Make project type selectable * Update Browse.vue * address changes * Quick create * Run linter * fix merge * Addressed changes * Installation improvements * Run lint * resourcepacks * automatic installation of dependencies * Fix bugs with search * Addressed changes * Run linter * Fixed direct install not working * Remove back to search * Update Index.vue * Addressed some changes * Shader fix * fix resetting * Update Browse.vue * fixed install not working properly * Update Index.vue --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Card } from 'omorphia'
|
||||
import { Avatar, Card } from 'omorphia'
|
||||
import { PlayIcon } from '@/assets/icons'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
|
||||
@@ -11,13 +11,32 @@ const props = defineProps({
|
||||
return {}
|
||||
},
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RouterLink :to="`/instance/${encodeURIComponent(props.instance.path)}`">
|
||||
<Card class="instance-card-item">
|
||||
<Card v-if="props.small" class="instance-small-card button-base">
|
||||
<Avatar
|
||||
:src="convertFileSrc(props.instance.metadata.icon)"
|
||||
:alt="props.instance.metadata.name"
|
||||
size="sm"
|
||||
/>
|
||||
<div class="instance-small-card__info">
|
||||
<span class="title">{{ props.instance.metadata.name }}</span>
|
||||
{{
|
||||
props.instance.metadata.loader.charAt(0).toUpperCase() +
|
||||
props.instance.metadata.loader.slice(1)
|
||||
}}
|
||||
{{ props.instance.metadata.game_version }}
|
||||
</div>
|
||||
</Card>
|
||||
<Card v-else class="instance-card-item">
|
||||
<img :src="convertFileSrc(props.instance.metadata.icon)" alt="Trending mod card" />
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.metadata.name }}</p>
|
||||
@@ -32,6 +51,27 @@ const props = defineProps({
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.instance-small-card {
|
||||
background-color: var(--color-bg) !important;
|
||||
padding: 1rem !important;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: min-content !important;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
.instance-small-card__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
297
theseus_gui/src/components/ui/InstanceInstallModal.vue
Normal file
297
theseus_gui/src/components/ui/InstanceInstallModal.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<script setup>
|
||||
import {
|
||||
Avatar,
|
||||
Modal,
|
||||
Button,
|
||||
DownloadIcon,
|
||||
PlusIcon,
|
||||
Card,
|
||||
UploadIcon,
|
||||
XIcon,
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
} from 'omorphia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { add_project_from_version as installMod, list } from '@/helpers/profile'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { checkInstalled, installVersionDependencies } from '@/helpers/utils'
|
||||
const router = useRouter()
|
||||
const versions = ref([])
|
||||
const project = ref('')
|
||||
const installModal = ref(null)
|
||||
const searchFilter = ref('')
|
||||
const showCreation = ref(false)
|
||||
const icon = ref(null)
|
||||
const name = ref(null)
|
||||
const display_icon = ref(null)
|
||||
const loader = ref(null)
|
||||
const gameVersion = ref(null)
|
||||
const creatingInstance = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: (projectId, selectedVersion) => {
|
||||
project.value = projectId
|
||||
versions.value = selectedVersion
|
||||
installModal.value.show()
|
||||
searchFilter.value = ''
|
||||
},
|
||||
})
|
||||
|
||||
const profiles = ref(await list().then(Object.values))
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
const version = versions.value.find((v) => {
|
||||
return (
|
||||
v.game_versions.includes(instance.metadata.game_version) &&
|
||||
(v.loaders.includes(instance.metadata.loader) || v.loaders.includes('minecraft'))
|
||||
)
|
||||
})
|
||||
|
||||
await installMod(instance.path, version.id)
|
||||
await installVersionDependencies(instance, version)
|
||||
|
||||
instance.installed = true
|
||||
instance.installing = false
|
||||
}
|
||||
|
||||
const filteredVersions = computed(() => {
|
||||
const filtered = profiles.value
|
||||
.filter((profile) => {
|
||||
return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) &&
|
||||
versions.value
|
||||
.flatMap((v) => v.loaders)
|
||||
.some((value) => value === profile.metadata.loader || value === 'minecraft')
|
||||
)
|
||||
})
|
||||
|
||||
filtered.map((profile) => {
|
||||
profile.installing = false
|
||||
profile.installed = checkInstalled(profile, project.value)
|
||||
})
|
||||
|
||||
return filtered
|
||||
})
|
||||
|
||||
const toggleCreation = () => {
|
||||
showCreation.value = !showCreation.value
|
||||
name.value = null
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
icon.value = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: 'Image',
|
||||
extensions: ['png', 'jpeg'],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
}
|
||||
|
||||
const createInstance = async () => {
|
||||
creatingInstance.value = true
|
||||
const id = await create(
|
||||
name.value,
|
||||
versions.value[0].game_versions[0],
|
||||
versions.value[0].loaders[0] !== 'forge' ||
|
||||
versions.value[0].loaders[0] !== 'fabric' ||
|
||||
versions.value[0].loaders[0] !== 'quilt'
|
||||
? versions.value[0].loaders[0]
|
||||
: 'vanilla',
|
||||
'latest',
|
||||
icon.value
|
||||
)
|
||||
|
||||
await installMod(id, versions.value[0].id)
|
||||
|
||||
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
|
||||
installModal.value.hide()
|
||||
creatingInstance.value = false
|
||||
}
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return name.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="installModal" header="Install mod to instance">
|
||||
<div class="modal-body">
|
||||
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
|
||||
<div class="profiles">
|
||||
<div v-for="profile in filteredVersions" :key="profile.metadata.name" class="option">
|
||||
<Button
|
||||
color="raised"
|
||||
class="profile-button"
|
||||
@click="$router.push(`/instance/${encodeURIComponent(profile.path)}`)"
|
||||
>
|
||||
<Avatar :src="convertFileSrc(profile.metadata.icon)" class="profile-image" />
|
||||
{{ profile.metadata.name }}
|
||||
</Button>
|
||||
<Button :disabled="profile.installed || profile.installing" @click="install(profile)">
|
||||
<DownloadIcon v-if="!profile.installed && !profile.installing" />
|
||||
<CheckIcon v-else-if="profile.installed" />
|
||||
{{ profile.installing ? 'Installing...' : profile.installed ? 'Installed' : 'Install' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Card v-if="showCreation" class="creation-card">
|
||||
<div class="creation-container">
|
||||
<div class="creation-icon">
|
||||
<Avatar size="md" class="icon" :src="display_icon" />
|
||||
<div class="creation-icon__description">
|
||||
<Button @click="upload_icon()">
|
||||
<UploadIcon />
|
||||
<span class="no-wrap"> Upload Icon </span>
|
||||
</Button>
|
||||
<Button @click="reset_icon()">
|
||||
<XIcon />
|
||||
<span class="no-wrap"> Remove Icon </span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-settings">
|
||||
<input v-model="name" type="text" placeholder="Name" class="creation-input" />
|
||||
<Button :disabled="creatingInstance === true || !check_valid" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="footer">
|
||||
<Button :color="showCreation ? '' : 'primary'" @click="toggleCreation()">
|
||||
<PlusIcon />
|
||||
{{ showCreation ? 'Hide New Instance' : 'Create new instance' }}
|
||||
</Button>
|
||||
<Button @click="installModal.hide()">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.creation-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
.creation-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.creation-icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
|
||||
.creation-icon__description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.creation-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.creation-dropdown {
|
||||
width: min-content !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.creation-settings {
|
||||
width: 100%;
|
||||
margin-left: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.profiles {
|
||||
max-height: 12rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.option {
|
||||
width: calc(100%);
|
||||
background: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
img {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.profile-button {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
--size: 2rem !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user