Merge branch 'master' into loading
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
# Windows has stack overflows when calling from Tauri, so we increase compiler size
|
||||
[target.'cfg(windows)']
|
||||
rustflags = ["-C", "link-args=/STACK:8388608"]
|
||||
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||
@@ -117,9 +117,13 @@ defineExpose({
|
||||
</section>
|
||||
</div>
|
||||
<div class="router-view">
|
||||
<Suspense>
|
||||
<RouterView />
|
||||
</Suspense>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense>
|
||||
<component :is="Component"></component>
|
||||
</Suspense>
|
||||
</template>
|
||||
</RouterView>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -133,10 +137,10 @@ defineExpose({
|
||||
overflow: hidden;
|
||||
|
||||
.view {
|
||||
width: calc(100% - 5rem);
|
||||
width: var(--view-width);
|
||||
|
||||
&.expanded {
|
||||
width: calc(100% - 12rem);
|
||||
width: var(--expanded-view-width);
|
||||
}
|
||||
|
||||
.appbar {
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
:root {
|
||||
font-family: var(--font-standard);
|
||||
color-scheme: dark;
|
||||
--view-width: calc(100% - 5rem);
|
||||
--expanded-view-width: calc(100% - 13rem);
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -23,23 +23,34 @@ const props = defineProps({
|
||||
},
|
||||
canPaginate: Boolean,
|
||||
})
|
||||
|
||||
const allowPagination = ref(false)
|
||||
const modsRow = ref(null)
|
||||
const newsRow = ref(null)
|
||||
// Remove after state is populated with real data
|
||||
|
||||
const shouldRenderNormalInstances = props.instances && props.instances?.length !== 0
|
||||
const shouldRenderNews = props.news && props.news?.length !== 0
|
||||
|
||||
const handlePaginationDisplay = () => {
|
||||
let parentsRow
|
||||
if (shouldRenderNormalInstances) parentsRow = modsRow.value
|
||||
if (shouldRenderNews) parentsRow = newsRow.value
|
||||
if (!parentsRow) return
|
||||
const children = parentsRow.children
|
||||
const lastChild = children[children.length - 1]
|
||||
const childBox = lastChild.getBoundingClientRect()
|
||||
if (childBox.x + childBox.width > window.innerWidth) allowPagination.value = true
|
||||
else allowPagination.value = false
|
||||
|
||||
// This is wrapped in a setTimeout because the HtmlCollection seems to struggle
|
||||
// with getting populated sometimes. It's a flaky error, but providing a bit of
|
||||
// wait-time for the below expressions has not failed thus-far.
|
||||
setTimeout(() => {
|
||||
const children = parentsRow.children
|
||||
const lastChild = children[children.length - 1]
|
||||
const childBox = lastChild?.getBoundingClientRect()
|
||||
|
||||
if (childBox?.x + childBox?.width > window.innerWidth && props.canPaginate)
|
||||
allowPagination.value = true
|
||||
else allowPagination.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (props.canPaginate) window.addEventListener('resize', handlePaginationDisplay)
|
||||
// Check if pagination should be rendered on mount
|
||||
@@ -48,6 +59,7 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
||||
})
|
||||
|
||||
const handleLeftPage = () => {
|
||||
if (shouldRenderNormalInstances) modsRow.value.scrollLeft -= 170
|
||||
else if (shouldRenderNews) newsRow.value.scrollLeft -= 170
|
||||
@@ -58,7 +70,7 @@ const handleRightPage = () => {
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="row">
|
||||
<div v-if="props.instances.length > 0" class="row">
|
||||
<div class="header">
|
||||
<p>{{ props.label }}</p>
|
||||
<hr aria-hidden="true" />
|
||||
@@ -70,7 +82,7 @@ const handleRightPage = () => {
|
||||
<section v-if="shouldRenderNormalInstances" ref="modsRow" class="instances">
|
||||
<Instance
|
||||
v-for="instance in props.instances"
|
||||
:key="instance.id"
|
||||
:key="instance?.project_id || instance?.id"
|
||||
display="card"
|
||||
:instance="instance"
|
||||
class="row-instance"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Avatar :size="expanded ? 'xs' : 'sm'" :src="selectedAccount?.profile_picture ?? ''" />
|
||||
<div v-show="expanded" class="avatar-text">
|
||||
<div class="text no-select">
|
||||
{{ selectedAccount.username }}
|
||||
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
||||
</div>
|
||||
<p class="no-select">
|
||||
<UsersIcon />
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { AnimatedLogo, Avatar, Card } from 'omorphia'
|
||||
import { shallowRef, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo } from 'omorphia'
|
||||
import { PlayIcon } from '@/assets/icons'
|
||||
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 {
|
||||
kill_by_uuid,
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
} from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -16,44 +27,173 @@ const props = defineProps({
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const playing = ref(false)
|
||||
|
||||
const uuid = ref(null)
|
||||
const modLoading = ref(false)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const seeInstance = async () => {
|
||||
const instancePath = props.instance.metadata
|
||||
? `/instance/${encodeURIComponent(props.instance.path)}`
|
||||
: `/project/${encodeURIComponent(props.instance.project_id)}`
|
||||
|
||||
await router.push(instancePath)
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningPaths = await get_all_running_profile_paths()
|
||||
|
||||
if (runningPaths.includes(props.instance.path)) {
|
||||
playing.value = true
|
||||
return
|
||||
}
|
||||
|
||||
playing.value = false
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
const install = async (e) => {
|
||||
e.stopPropagation()
|
||||
modLoading.value = true
|
||||
const [data, versions] = await Promise.all([
|
||||
ofetch(
|
||||
`https://api.modrinth.com/v2/project/${
|
||||
props.instance.metadata
|
||||
? props.instance.metadata?.linked_data?.project_id
|
||||
: props.instance.project_id
|
||||
}`
|
||||
).then(shallowRef),
|
||||
ofetch(
|
||||
`https://api.modrinth.com/v2/project/${
|
||||
props.instance.metadata
|
||||
? props.instance.metadata?.linked_dadta?.project_id
|
||||
: props.instance.project_id
|
||||
}/version`
|
||||
).then(shallowRef),
|
||||
])
|
||||
|
||||
if (data.value.project_type === 'modpack') {
|
||||
const packs = Object.values(await list())
|
||||
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === data.value.id)
|
||||
) {
|
||||
await pack_install(versions.value[0].id)
|
||||
} else confirmModal.value.show(versions.value[0].id)
|
||||
}
|
||||
|
||||
modLoading.value = false
|
||||
// TODO: Add condition for installing a mod
|
||||
}
|
||||
|
||||
const play = async (e) => {
|
||||
e.stopPropagation()
|
||||
modLoading.value = true
|
||||
uuid.value = await run(props.instance.path)
|
||||
modLoading.value = false
|
||||
playing.value = true
|
||||
}
|
||||
|
||||
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)
|
||||
uuid.value = uuids[0]
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u))
|
||||
} else await kill_by_uuid(uuid.value) // 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)
|
||||
}
|
||||
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
await process_listener((e) => {
|
||||
if (e.event === 'Finished' && e.uuid == uuid.value) playing.value = false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RouterLink :to="`/instance/${encodeURIComponent(props.instance.path)}`">
|
||||
<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>
|
||||
<p class="description">
|
||||
{{ props.instance.metadata.loader }} {{ props.instance.metadata.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="cta" :class="{ loading: !instance.installed }">
|
||||
<PlayIcon v-if="instance.installed" />
|
||||
<AnimatedLogo v-else class="loading-icon" />
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
<div class="instance">
|
||||
<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 button-base"
|
||||
@click="seeInstance"
|
||||
@mouseenter="checkProcess"
|
||||
>
|
||||
<Avatar
|
||||
size="lg"
|
||||
:src="
|
||||
props.instance.metadata
|
||||
? convertFileSrc(props.instance.metadata?.icon)
|
||||
: props.instance.icon_url
|
||||
"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.metadata?.name || props.instance.title }}</p>
|
||||
<p class="description">
|
||||
{{ props.instance.metadata?.loader }}
|
||||
{{ props.instance.metadata?.game_version || props.instance.latest_version }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||
class="install cta button-base"
|
||||
@click="play"
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading">
|
||||
<AnimatedLogo class="loading" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="stop"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<XIcon />
|
||||
</div>
|
||||
<div v-else class="install cta buttonbase" @click="install"><SaveIcon /></div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.instance-small-card {
|
||||
background-color: var(--color-bg) !important;
|
||||
padding: 1rem !important;
|
||||
@@ -75,21 +215,106 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
|
||||
.instance {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: 4.5rem;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
background: hsl(220, 11%, 11%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
.instance:hover {
|
||||
.instance-card-item {
|
||||
background: hsl(0, 0%, 91%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.install {
|
||||
background: var(--color-brand);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.stop {
|
||||
background: var(--color-red);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cta.loading {
|
||||
background: hsl(220, 11%, 10%) !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.loading {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
.instance-card-item {
|
||||
background: hsl(0, 0%, 100%) !important;
|
||||
|
||||
&:hover {
|
||||
background: hsl(0, 0%, 91%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cta {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-lg);
|
||||
z-index: 41;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: 1rem;
|
||||
bottom: 3.5rem;
|
||||
opacity: 0;
|
||||
transition: 0.3s ease-in-out bottom, 0.1s ease-in-out opacity !important;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: none !important; /* overrides button-base class */
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 0.75rem;
|
||||
transition: 0.1s ease-in-out all;
|
||||
padding: 0.75rem !important; /* overrides card class */
|
||||
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||
background: hsl(220, 11%, 17%) !important;
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.85);
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: 4.5rem;
|
||||
}
|
||||
filter: brightness(1) !important;
|
||||
background: hsl(220, 11%, 11%) !important;
|
||||
}
|
||||
|
||||
.cta {
|
||||
@@ -137,22 +362,24 @@ const props = defineProps({
|
||||
border-radius: var(--radius-sm);
|
||||
filter: none !important;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
//max-width: 10rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 110%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-base);
|
||||
display: -webkit-box;
|
||||
|
||||
@@ -36,7 +36,7 @@ import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
}
|
||||
loader_uuid: unique identification of the loading bar
|
||||
fraction: number, (as a fraction of 1, how much we'vel oaded so far). If null, by convention, loading is finished
|
||||
fraction: number, (as a fraction of 1, how much we've loaded so far). If null, by convention, loading is finished
|
||||
message: message to display to the user
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,34 +1,67 @@
|
||||
<script setup>
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import {ref, onUnmounted, shallowRef} from 'vue'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { useRoute } from 'vue-router'
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { profile_listener } from '@/helpers/events'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
|
||||
const featuredModpacks = ref({})
|
||||
const featuredMods = ref({})
|
||||
const filter = ref('')
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const loading = ref(true)
|
||||
const recentInstances = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
recentInstances.value = Object.values(await list())
|
||||
loading.value = false
|
||||
})
|
||||
const recentInstances = shallowRef(Object.values(await list()))
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
||||
|
||||
const getInstances = async () => {
|
||||
filter.value = ''
|
||||
const profiles = await list()
|
||||
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 '
|
||||
})
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
const response = await ofetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`
|
||||
)
|
||||
featuredModpacks.value = response.hits
|
||||
}
|
||||
const getFeaturedMods = async () => {
|
||||
const response = await ofetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows&filters=${filter.value}`
|
||||
)
|
||||
featuredMods.value = response.hits
|
||||
}
|
||||
|
||||
await getInstances()
|
||||
await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
|
||||
|
||||
const unlisten = await profile_listener(async (e) => {
|
||||
if (e.event === 'edited') {
|
||||
await getInstances()
|
||||
await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<SplashScreen v-if="loading" />
|
||||
<div v-else class="page-container">
|
||||
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
||||
<RowDisplay label="Popular packs" :instances="recentInstances" :can-paginate="true" />
|
||||
<RowDisplay label="Test" :instances="recentInstances" :can-paginate="true" />
|
||||
</div>
|
||||
</transition>
|
||||
<div class="page-container">
|
||||
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
||||
<RowDisplay label="Popular packs" :instances="featuredModpacks" :can-paginate="true" />
|
||||
<RowDisplay label="Popular mods" :instances="featuredMods" :can-paginate="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,53 +1,43 @@
|
||||
<script setup>
|
||||
import { shallowRef } from 'vue'
|
||||
import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { list } from '@/helpers/profile'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { loading_listener } from '@/helpers/events.js'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const loading = ref(true)
|
||||
const instances = ref(null)
|
||||
const loadingInstances = ref(null)
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
||||
|
||||
onMounted(async () => {
|
||||
instances.value = Object.values(await list())
|
||||
loadingInstances.value = Object.values(await progress_bars_list())
|
||||
loading.value = false
|
||||
})
|
||||
const profiles = await list()
|
||||
const instances = shallowRef(
|
||||
Object.values(profiles).filter((prof) => !prof.metadata.linked_project_id)
|
||||
)
|
||||
const modpacks = shallowRef(
|
||||
Object.values(profiles).filter((prof) => prof.metadata.linked_project_id)
|
||||
)
|
||||
|
||||
loading_listener(async (profile) => {
|
||||
console.log(profile)
|
||||
instances.value = Object.values(await list())
|
||||
loadingInstances.value = Object.values(await progress_bars_list())
|
||||
if (profile.event === 'loaded') {
|
||||
const profiles = await list()
|
||||
instances.value = Object.values(profiles).filter(
|
||||
(prof) => !prof.metadata.linked_project_id
|
||||
)
|
||||
modpacks.value = Object.values(profiles).filter(
|
||||
(prof) => prof.metadata.linked_project_id
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<SplashScreen v-if="loading" />
|
||||
<div v-else>
|
||||
<GridDisplay label="Instances" :instances="instances" />
|
||||
<GridDisplay label="Modpacks" :instances="instances" />
|
||||
</div>
|
||||
</transition>
|
||||
<div>
|
||||
<GridDisplay label="Instances" :instances="instances" />
|
||||
<GridDisplay label="Modpacks" :instances="modpacks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -360,7 +360,7 @@ const setJavaInstall = (javaInstall) => {
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.concurrent-downloads {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<SplashScreen v-if="loading" />
|
||||
<div v-else class="instance-container">
|
||||
<div class="instance-container">
|
||||
<div class="side-cards">
|
||||
<Card class="instance-card">
|
||||
<Avatar size="lg" :src="convertFileSrc(instance.metadata.icon)" />
|
||||
@@ -12,20 +10,33 @@
|
||||
</span>
|
||||
</div>
|
||||
<span class="button-group">
|
||||
<Button
|
||||
:color="instance.installed ? 'primary' : ''"
|
||||
class="instance-button"
|
||||
:disabled="!instance.installed"
|
||||
@click="run($route.params.id)"
|
||||
>
|
||||
<PlayIcon v-if="instance.installed" />
|
||||
<AnimatedLogo v-else class="loading-icon" />
|
||||
{{ instance.installed ? 'Play' : 'Installing' }}
|
||||
</Button>
|
||||
<Button class="instance-button" icon-only>
|
||||
<OpenFolderIcon />
|
||||
</Button>
|
||||
</span>
|
||||
<Button
|
||||
v-if="playing === true"
|
||||
color="danger"
|
||||
class="instance-button"
|
||||
@click="stopInstance"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<XIcon />
|
||||
Stop
|
||||
</Button>
|
||||
<Button
|
||||
v-else-if="playing === false && loading === false"
|
||||
color="primary"
|
||||
class="instance-button"
|
||||
@click="startInstance"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<PlayIcon />
|
||||
Play
|
||||
</Button>
|
||||
<Button v-else-if="loading === true && playing === false" disabled class="instance-button"
|
||||
>Loading...</Button
|
||||
>
|
||||
<Button class="instance-button" icon-only @click="open({ defaultPath: instance.path })">
|
||||
<OpenFolderIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Card>
|
||||
<div class="pages-list">
|
||||
<RouterLink :to="`/instance/${encodeURIComponent($route.params.id)}/`" class="btn">
|
||||
@@ -45,47 +56,36 @@
|
||||
<div class="content">
|
||||
<Promotion />
|
||||
<router-view :instance="instance" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
BoxIcon,
|
||||
SettingsIcon,
|
||||
FileIcon,
|
||||
Button,
|
||||
Avatar,
|
||||
Card,
|
||||
Promotion,
|
||||
AnimatedLogo,
|
||||
} from 'omorphia'
|
||||
import { BoxIcon, SettingsIcon, FileIcon, XIcon, Button, Avatar, Card, Promotion } from 'omorphia'
|
||||
import { PlayIcon, OpenFolderIcon } from '@/assets/icons'
|
||||
import { get, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
kill_by_uuid,
|
||||
} from '@/helpers/process'
|
||||
import {process_listener, profile_listener} from '@/helpers/events'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useSearch } from '@/store/search'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { useBreadcrumbs, useSearch } from '@/store/state'
|
||||
|
||||
const route = useRoute()
|
||||
const searchStore = useSearch()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const instance = ref(null)
|
||||
const loading = ref(true)
|
||||
const instance = ref(await get(route.params.id))
|
||||
|
||||
onMounted(async () => {
|
||||
instance.value = await get(route.params.id)
|
||||
searchStore.instanceContext = instance.value
|
||||
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
||||
breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
link: route.path,
|
||||
})
|
||||
loading.value = false
|
||||
searchStore.instanceContext = instance.value
|
||||
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
||||
breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
link: route.path,
|
||||
})
|
||||
|
||||
profile_listener(async (event) => {
|
||||
@@ -93,6 +93,53 @@ profile_listener(async (event) => {
|
||||
instance.value = await get(route.params.id)
|
||||
}
|
||||
})
|
||||
|
||||
const uuid = ref(null)
|
||||
const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
const startInstance = async () => {
|
||||
loading.value = true
|
||||
uuid.value = await run(route.params.id)
|
||||
loading.value = false
|
||||
playing.value = true
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningPaths = await get_all_running_profile_paths()
|
||||
if (runningPaths.includes(instance.value.path)) {
|
||||
playing.value = true
|
||||
return
|
||||
}
|
||||
|
||||
playing.value = false
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
await checkProcess()
|
||||
|
||||
const stopInstance = async () => {
|
||||
playing.value = false
|
||||
|
||||
try {
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(instance.value.path)
|
||||
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u))
|
||||
} else await kill_by_uuid(uuid.value)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
if (e.event === 'Finished' && uuid.value === e.uuid) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
Reference in New Issue
Block a user