Wire up homepage & library (#83)
* Top 10 packs & mods by follows plugged into home page. Modpacks installable. * Only displays row if packs are present. Confirmation modal added. Displays play or X ctas. * Fixes attr ordering. * Rewires library page. Adds loader. * Updates kill_by_pid to kill_by_uuid. * Starts loading animation when installing on homepage. * Changes RowDisplay key. Polish. * Removes loader. Fixes InstallConfirmModal. * Removes loader. Polishing. * Z-index changes. * Z-index changes. * Fixes content going off screen. * Styling changes. * Filters out projects already installed on the home page. * Wires up instance.vue, homepage, and appbar to process API. * Cleans up process handling. App bar partially hooked up. * Removes scoped in Settings to fix AnimatedLogo. Adds loader to Instance. * Moves ctas outside of card. * Adds mouse over to Stop btn. * Removes unnecessary code. Fixes uuid reset. * Wires up Instance.vue to process API. * Removes appbar mod count. Updates code to use new linked_data and updated events. * Switches load_listener to profile_listener. Unlistens on unmount. * Cleans up instance card styling. * Fixes margin with uncollapsed navbar. Ensures RowDisplay has data. * Updates profile_listener. Increases stack size. * Provides more margin when navbar is expanded. * fix proper * Re-adds calculated width and height. Fixes navbar. * Increases stack size further. Navbar is not absolute. View width made into var. * Ensures the specific isntance for a killed process is set to off. * fix menu when not logged in --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
parent
da4fc1c835
commit
ba20c482bb
@ -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"]
|
||||
@ -1,11 +1,10 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, Button } from 'omorphia'
|
||||
import { useTheming } from '@/store/state'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { list } from '@/helpers/profile'
|
||||
import { get } from '@/helpers/settings'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
@ -17,16 +16,6 @@ onMounted(async () => {
|
||||
themeStore.setThemeState(settings)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
})
|
||||
|
||||
const installedMods = ref(0)
|
||||
list().then(
|
||||
(profiles) =>
|
||||
(installedMods.value = Object.values(profiles).reduce(
|
||||
(acc, val) => acc + Object.keys(val.projects).length,
|
||||
0
|
||||
))
|
||||
)
|
||||
// TODO: add event when profiles update to update installed mods count
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -117,9 +106,13 @@ list().then(
|
||||
</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 +126,10 @@ list().then(
|
||||
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 { 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,41 +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"><PlayIcon /></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;
|
||||
@ -72,68 +215,129 @@ 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 {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--color-brand);
|
||||
border-radius: var(--radius-lg);
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: 1rem;
|
||||
bottom: 3.5rem;
|
||||
opacity: 0;
|
||||
transition: 0.3s ease-in-out bottom, 0.1s ease-in-out opacity;
|
||||
cursor: pointer;
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
&:hover {
|
||||
filter: brightness(0.75);
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-sm);
|
||||
filter: none !important;
|
||||
aspect-ratio: 1;
|
||||
.mod-image {
|
||||
border-radius: 1.5rem !important;
|
||||
}
|
||||
|
||||
.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,24 +1,66 @@
|
||||
<script setup>
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { shallowRef } from 'vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { ref, shallowRef, onUnmounted } from 'vue'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { useRoute } from 'vue-router'
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { profile_listener } from '@/helpers/events'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
|
||||
const featuredModpacks = ref({})
|
||||
const featuredMods = ref({})
|
||||
const filter = ref('')
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const profiles = await list()
|
||||
const recentInstances = shallowRef(Object.values(profiles))
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
||||
|
||||
const recentInstances = shallowRef()
|
||||
|
||||
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>
|
||||
<div class="page-container">
|
||||
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
||||
<RowDisplay label="Popular packs" :instances="recentInstances" :can-paginate="true" />
|
||||
<RowDisplay label="Test" :instances="recentInstances" :can-paginate="true" />
|
||||
<RowDisplay label="Popular packs" :instances="featuredModpacks" :can-paginate="true" />
|
||||
<RowDisplay label="Popular mods" :instances="featuredMods" :can-paginate="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -1,22 +1,29 @@
|
||||
<script setup>
|
||||
import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { shallowRef } from 'vue'
|
||||
import { list } from '@/helpers/profile'
|
||||
import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const profiles = await list()
|
||||
const instances = shallowRef(Object.values(profiles))
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
||||
|
||||
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)
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<GridDisplay label="Instances" :instances="instances" />
|
||||
<GridDisplay label="Modpacks" :instances="instances" />
|
||||
<GridDisplay label="Modpacks" :instances="modpacks" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@ -347,7 +347,7 @@ const setJavaInstall = (javaInstall) => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.concurrent-downloads {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user