You've already forked AstralRinth
forked from didirus/AstralRinth
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:
@@ -1,3 +1,3 @@
|
|||||||
# Windows has stack overflows when calling from Tauri, so we increase compiler size
|
# Windows has stack overflows when calling from Tauri, so we increase compiler size
|
||||||
[target.'cfg(windows)']
|
[target.'cfg(windows)']
|
||||||
rustflags = ["-C", "link-args=/STACK:8388608"]
|
rustflags = ["-C", "link-args=/STACK:16777220"]
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { RouterView, RouterLink } from 'vue-router'
|
import { RouterView, RouterLink } from 'vue-router'
|
||||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, Button } from 'omorphia'
|
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, Button } from 'omorphia'
|
||||||
import { useTheming } from '@/store/state'
|
import { useTheming } from '@/store/state'
|
||||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||||
import { list } from '@/helpers/profile'
|
|
||||||
import { get } from '@/helpers/settings'
|
import { get } from '@/helpers/settings'
|
||||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||||
@@ -17,16 +16,6 @@ onMounted(async () => {
|
|||||||
themeStore.setThemeState(settings)
|
themeStore.setThemeState(settings)
|
||||||
themeStore.collapsedNavigation = collapsed_navigation
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -117,9 +106,13 @@ list().then(
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="router-view">
|
<div class="router-view">
|
||||||
<Suspense>
|
<RouterView v-slot="{ Component }">
|
||||||
<RouterView />
|
<template v-if="Component">
|
||||||
</Suspense>
|
<Suspense>
|
||||||
|
<component :is="Component"></component>
|
||||||
|
</Suspense>
|
||||||
|
</template>
|
||||||
|
</RouterView>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,10 +126,10 @@ list().then(
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.view {
|
.view {
|
||||||
width: calc(100% - 5rem);
|
width: var(--view-width);
|
||||||
|
|
||||||
&.expanded {
|
&.expanded {
|
||||||
width: calc(100% - 12rem);
|
width: var(--expanded-view-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.appbar {
|
.appbar {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: var(--font-standard);
|
font-family: var(--font-standard);
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
--view-width: calc(100% - 5rem);
|
||||||
|
--expanded-view-width: calc(100% - 13rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -23,23 +23,34 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
canPaginate: Boolean,
|
canPaginate: Boolean,
|
||||||
})
|
})
|
||||||
|
|
||||||
const allowPagination = ref(false)
|
const allowPagination = ref(false)
|
||||||
const modsRow = ref(null)
|
const modsRow = ref(null)
|
||||||
const newsRow = ref(null)
|
const newsRow = ref(null)
|
||||||
// Remove after state is populated with real data
|
|
||||||
const shouldRenderNormalInstances = props.instances && props.instances?.length !== 0
|
const shouldRenderNormalInstances = props.instances && props.instances?.length !== 0
|
||||||
const shouldRenderNews = props.news && props.news?.length !== 0
|
const shouldRenderNews = props.news && props.news?.length !== 0
|
||||||
|
|
||||||
const handlePaginationDisplay = () => {
|
const handlePaginationDisplay = () => {
|
||||||
let parentsRow
|
let parentsRow
|
||||||
if (shouldRenderNormalInstances) parentsRow = modsRow.value
|
if (shouldRenderNormalInstances) parentsRow = modsRow.value
|
||||||
if (shouldRenderNews) parentsRow = newsRow.value
|
if (shouldRenderNews) parentsRow = newsRow.value
|
||||||
if (!parentsRow) return
|
if (!parentsRow) return
|
||||||
const children = parentsRow.children
|
|
||||||
const lastChild = children[children.length - 1]
|
// This is wrapped in a setTimeout because the HtmlCollection seems to struggle
|
||||||
const childBox = lastChild.getBoundingClientRect()
|
// with getting populated sometimes. It's a flaky error, but providing a bit of
|
||||||
if (childBox.x + childBox.width > window.innerWidth) allowPagination.value = true
|
// wait-time for the below expressions has not failed thus-far.
|
||||||
else allowPagination.value = false
|
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(() => {
|
onMounted(() => {
|
||||||
if (props.canPaginate) window.addEventListener('resize', handlePaginationDisplay)
|
if (props.canPaginate) window.addEventListener('resize', handlePaginationDisplay)
|
||||||
// Check if pagination should be rendered on mount
|
// Check if pagination should be rendered on mount
|
||||||
@@ -48,6 +59,7 @@ onMounted(() => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleLeftPage = () => {
|
const handleLeftPage = () => {
|
||||||
if (shouldRenderNormalInstances) modsRow.value.scrollLeft -= 170
|
if (shouldRenderNormalInstances) modsRow.value.scrollLeft -= 170
|
||||||
else if (shouldRenderNews) newsRow.value.scrollLeft -= 170
|
else if (shouldRenderNews) newsRow.value.scrollLeft -= 170
|
||||||
@@ -58,7 +70,7 @@ const handleRightPage = () => {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="row">
|
<div v-if="props.instances.length > 0" class="row">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p>{{ props.label }}</p>
|
<p>{{ props.label }}</p>
|
||||||
<hr aria-hidden="true" />
|
<hr aria-hidden="true" />
|
||||||
@@ -70,7 +82,7 @@ const handleRightPage = () => {
|
|||||||
<section v-if="shouldRenderNormalInstances" ref="modsRow" class="instances">
|
<section v-if="shouldRenderNormalInstances" ref="modsRow" class="instances">
|
||||||
<Instance
|
<Instance
|
||||||
v-for="instance in props.instances"
|
v-for="instance in props.instances"
|
||||||
:key="instance.id"
|
:key="instance?.project_id || instance?.id"
|
||||||
display="card"
|
display="card"
|
||||||
:instance="instance"
|
:instance="instance"
|
||||||
class="row-instance"
|
class="row-instance"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<Avatar :size="expanded ? 'xs' : 'sm'" :src="selectedAccount?.profile_picture ?? ''" />
|
<Avatar :size="expanded ? 'xs' : 'sm'" :src="selectedAccount?.profile_picture ?? ''" />
|
||||||
<div v-show="expanded" class="avatar-text">
|
<div v-show="expanded" class="avatar-text">
|
||||||
<div class="text no-select">
|
<div class="text no-select">
|
||||||
{{ selectedAccount.username }}
|
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
||||||
</div>
|
</div>
|
||||||
<p class="no-select">
|
<p class="no-select">
|
||||||
<UsersIcon />
|
<UsersIcon />
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink } from 'vue-router'
|
import { shallowRef, ref } from 'vue'
|
||||||
import { Avatar, Card } from 'omorphia'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { ofetch } from 'ofetch'
|
||||||
|
import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo } from 'omorphia'
|
||||||
import { PlayIcon } from '@/assets/icons'
|
import { PlayIcon } from '@/assets/icons'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
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({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
@@ -16,41 +27,173 @@ const props = defineProps({
|
|||||||
default: false,
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="instance">
|
||||||
<RouterLink :to="`/instance/${encodeURIComponent(props.instance.path)}`">
|
<Card v-if="props.small" class="instance-small-card button-base">
|
||||||
<Card v-if="props.small" class="instance-small-card button-base">
|
<Avatar
|
||||||
<Avatar
|
:src="convertFileSrc(props.instance.metadata.icon)"
|
||||||
:src="convertFileSrc(props.instance.metadata.icon)"
|
:alt="props.instance.metadata.name"
|
||||||
:alt="props.instance.metadata.name"
|
size="sm"
|
||||||
size="sm"
|
/>
|
||||||
/>
|
<div class="instance-small-card__info">
|
||||||
<div class="instance-small-card__info">
|
<span class="title">{{ props.instance.metadata.name }}</span>
|
||||||
<span class="title">{{ props.instance.metadata.name }}</span>
|
{{
|
||||||
{{
|
props.instance.metadata.loader.charAt(0).toUpperCase() +
|
||||||
props.instance.metadata.loader.charAt(0).toUpperCase() +
|
props.instance.metadata.loader.slice(1)
|
||||||
props.instance.metadata.loader.slice(1)
|
}}
|
||||||
}}
|
{{ props.instance.metadata.game_version }}
|
||||||
{{ props.instance.metadata.game_version }}
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</Card>
|
<Card
|
||||||
<Card v-else class="instance-card-item">
|
v-else
|
||||||
<img :src="convertFileSrc(props.instance.metadata.icon)" alt="Trending mod card" />
|
class="instance-card-item button-base"
|
||||||
<div class="project-info">
|
@click="seeInstance"
|
||||||
<p class="title">{{ props.instance.metadata.name }}</p>
|
@mouseenter="checkProcess"
|
||||||
<p class="description">
|
>
|
||||||
{{ props.instance.metadata.loader }} {{ props.instance.metadata.game_version }}
|
<Avatar
|
||||||
</p>
|
size="lg"
|
||||||
</div>
|
:src="
|
||||||
<div class="cta"><PlayIcon /></div>
|
props.instance.metadata
|
||||||
</Card>
|
? convertFileSrc(props.instance.metadata?.icon)
|
||||||
</RouterLink>
|
: 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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.instance-small-card {
|
.instance-small-card {
|
||||||
background-color: var(--color-bg) !important;
|
background-color: var(--color-bg) !important;
|
||||||
padding: 1rem !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 {
|
.instance-card-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem !important; /* overrides card class */
|
||||||
transition: 0.1s ease-in-out all;
|
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||||
|
background: hsl(220, 11%, 17%) !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: brightness(0.85);
|
filter: brightness(1) !important;
|
||||||
.cta {
|
background: hsl(220, 11%, 11%) !important;
|
||||||
opacity: 1;
|
|
||||||
bottom: 4.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cta {
|
.mod-image {
|
||||||
position: absolute;
|
border-radius: 1.5rem !important;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-info {
|
.project-info {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: var(--color-contrast);
|
color: var(--color-contrast);
|
||||||
//max-width: 10rem;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 110%;
|
line-height: 110%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: var(--color-base);
|
color: var(--color-base);
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { listen } from '@tauri-apps/api/event'
|
|||||||
|
|
||||||
}
|
}
|
||||||
loader_uuid: unique identification of the loading bar
|
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
|
message: message to display to the user
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,24 +1,66 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import RowDisplay from '@/components/RowDisplay.vue'
|
import { ref, shallowRef, onUnmounted } from 'vue'
|
||||||
import { shallowRef } from 'vue'
|
import { ofetch } from 'ofetch'
|
||||||
import { list } from '@/helpers/profile.js'
|
|
||||||
import { useRoute } from 'vue-router'
|
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'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
|
||||||
|
const featuredModpacks = ref({})
|
||||||
|
const featuredMods = ref({})
|
||||||
|
const filter = ref('')
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
const profiles = await list()
|
|
||||||
const recentInstances = shallowRef(Object.values(profiles))
|
|
||||||
|
|
||||||
breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
<RowDisplay label="Jump back in" :instances="recentInstances" :can-paginate="false" />
|
||||||
<RowDisplay label="Popular packs" :instances="recentInstances" :can-paginate="true" />
|
<RowDisplay label="Popular packs" :instances="featuredModpacks" :can-paginate="true" />
|
||||||
<RowDisplay label="Test" :instances="recentInstances" :can-paginate="true" />
|
<RowDisplay label="Popular mods" :instances="featuredMods" :can-paginate="true" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import GridDisplay from '@/components/GridDisplay.vue'
|
|
||||||
import { shallowRef } from '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 { useRoute } from 'vue-router'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
const profiles = await list()
|
|
||||||
const instances = shallowRef(Object.values(profiles))
|
|
||||||
|
|
||||||
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<GridDisplay label="Instances" :instances="instances" />
|
<GridDisplay label="Instances" :instances="instances" />
|
||||||
<GridDisplay label="Modpacks" :instances="instances" />
|
<GridDisplay label="Modpacks" :instances="modpacks" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ const setJavaInstall = (javaInstall) => {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.concurrent-downloads {
|
.concurrent-downloads {
|
||||||
width: 80% !important;
|
width: 80% !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user