split from page components

This commit is contained in:
Carter 2024-01-30 12:29:27 -08:00
parent a48186fa63
commit 694ee7e89f
No known key found for this signature in database
7 changed files with 165 additions and 40 deletions

View File

@ -13,9 +13,11 @@ import {
Card, Card,
TextLogo, TextLogo,
PlusIcon, PlusIcon,
HamburgerIcon,
} from 'omorphia' } from 'omorphia'
import { useLoading, useTheming } from '@/store/state' import { useLoading, useTheming } from '@/store/state'
import { useInstances } from '@/store/instances'
// import AccountsCard from './components/ui/AccountsCard.vue' // import AccountsCard from './components/ui/AccountsCard.vue'
import AccountDropdown from '@/components/ui/platform/AccountDropdown.vue' import AccountDropdown from '@/components/ui/platform/AccountDropdown.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
@ -38,6 +40,7 @@ import { useDisableClicks } from '@/composables/click.js'
import { openExternal } from '@/helpers/external.js' import { openExternal } from '@/helpers/external.js'
import { await_sync, check_safe_loading_bars_complete } from '@/helpers/state.js' import { await_sync, check_safe_loading_bars_complete } from '@/helpers/state.js'
import { install_from_file } from '@/helpers/pack.js' import { install_from_file } from '@/helpers/pack.js'
import { iconPathAsUrl } from '@/helpers/icon'
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue' import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
@ -50,6 +53,7 @@ import { TauriEvent } from '@tauri-apps/api/event'
import { confirm } from '@tauri-apps/api/dialog' import { confirm } from '@tauri-apps/api/dialog'
import { type } from '@tauri-apps/api/os' import { type } from '@tauri-apps/api/os'
import { appWindow } from '@tauri-apps/api/window' import { appWindow } from '@tauri-apps/api/window'
import { storeToRefs } from 'pinia'
const themeStore = useTheming() const themeStore = useTheming()
const urlModal = ref(null) const urlModal = ref(null)
@ -68,6 +72,9 @@ const onboardingVideo = ref()
const failureText = ref(null) const failureText = ref(null)
const os = ref('') const os = ref('')
const instances = useInstances()
const { instancesByPlayed } = storeToRefs(instances)
defineExpose({ defineExpose({
initialize: async () => { initialize: async () => {
isLoading.value = false isLoading.value = false
@ -275,15 +282,8 @@ const toggleSidebar = () => {
> >
<div class="pages-list"> <div class="pages-list">
<div class="square-collapsed-space"> <div class="square-collapsed-space">
<Button <Button transparent icon-only class="collapsed-button" @click="toggleSidebar">
v-tooltip="'Toggle sidebar'" <HamburgerIcon />
transparent
icon-only
class="collapsed-button"
@click="toggleSidebar"
>
<PlusIcon />
<span class="collapsed-button__label">Collapse</span>
</Button> </Button>
</div> </div>
</div> </div>
@ -316,9 +316,23 @@ const toggleSidebar = () => {
</suspense> </suspense>
</div> </div>
</div> </div>
<div class="divider">
<hr />
</div>
<div class="instances pages-list"> <div class="instances pages-list">
<RouterLink v-tooltip="'Meow'" to="/undefined" class="btn icon-only collapsed-button"> <RouterLink
Meow v-for="instance in instancesByPlayed"
:key="instance.id"
v-tooltip="instance.metadata.name"
:to="`/instance/${encodeURIComponent(instance.path)}`"
class="btn icon-only collapsed-button"
>
<img
class="collapsed-button__icon"
:src="iconPathAsUrl(instance.metadata?.icon)"
:alt="instance.metadata.name"
/>
<span class="collapsed-button__label">{{ instance.metadata.name }}</span>
</RouterLink> </RouterLink>
</div> </div>
<div class="settings pages-list"> <div class="settings pages-list">
@ -358,7 +372,6 @@ const toggleSidebar = () => {
<TextLogo class="logo" :animate="false" /> <TextLogo class="logo" :animate="false" />
</router-link> </router-link>
<Breadcrumbs after-logo data-tauri-drag-region /> <Breadcrumbs after-logo data-tauri-drag-region />
<!-- <pre><code>{{ JSON.stringify(breadcrumbs.path) }}</code></pre> -->
</section> </section>
<section class="mod-stats"> <section class="mod-stats">
<Suspense> <Suspense>
@ -467,6 +480,8 @@ const toggleSidebar = () => {
.container { .container {
--appbar-height: 4.5rem; --appbar-height: 4.5rem;
--sidebar-gap: 0.35rem;
--sidebar-width: 4.5rem; --sidebar-width: 4.5rem;
--sidebar-open-width: 15rem; --sidebar-open-width: 15rem;
--sidebar-padding: 0.75rem; --sidebar-padding: 0.75rem;
@ -602,9 +617,34 @@ const toggleSidebar = () => {
} }
} }
.divider {
height: auto;
width: 100%;
hr {
background-color: var(--color-button-bg);
border: none;
color: var(--color-button-bg);
height: 1px;
width: 100%;
margin: 0;
}
margin-top: var(--sidebar-gap);
// div should always have + 1 --sidebar-gap margin to the bottom to be equal
margin-bottom: calc(var(--sidebar-gap) * 2);
padding-left: var(--sidebar-padding);
padding-right: var(--sidebar-padding);
}
.instances { .instances {
height: 100%; flex: 1;
flex-grow: 1;
flex-flow: column wrap; // This hides any elements that aren't fully visible
overflow: hidden;
} }
.pages-list { .pages-list {
@ -615,7 +655,7 @@ const toggleSidebar = () => {
width: 100%; width: 100%;
gap: 0.35rem; gap: var(--sidebar-gap);
.page-item, .page-item,
a { a {
@ -655,6 +695,8 @@ const toggleSidebar = () => {
height: var(--sidebar-button-size); height: var(--sidebar-button-size);
width: 100%; width: 100%;
flex-shrink: 0;
padding: var(--sidebar-padding) !important; padding: var(--sidebar-padding) !important;
border-radius: 99999px; border-radius: 99999px;
box-shadow: none; box-shadow: none;
@ -670,6 +712,8 @@ const toggleSidebar = () => {
height: var(--sidebar-icon-size); height: var(--sidebar-icon-size);
flex-shrink: 0; flex-shrink: 0;
border-radius: var(--radius-xs);
} }
.collapsed-button__label { .collapsed-button__label {

View File

@ -0,0 +1,12 @@
import { convertFileSrc } from '@tauri-apps/api/tauri'
export const iconPathAsUrl = (iconPath) => {
if (!iconPath) {
return ''
}
const startsWithHttp = iconPath.startsWith('http')
if (startsWithHttp) {
return iconPath
}
return convertFileSrc(iconPath)
}

View File

@ -1,14 +1,13 @@
<script setup> <script setup>
import { ref, onUnmounted, shallowRef, computed } from 'vue' import { ref, onUnmounted, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import RowDisplay from '@/components/RowDisplay.vue' import RowDisplay from '@/components/RowDisplay.vue'
import { list } from '@/helpers/profile.js'
import { offline_listener, profile_listener } from '@/helpers/events' import { offline_listener, profile_listener } from '@/helpers/events'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useFetch } from '@/helpers/fetch.js' import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
import dayjs from 'dayjs'
import { isOffline } from '@/helpers/utils' import { isOffline } from '@/helpers/utils'
import { useInstances } from '@/store/instances'
import { storeToRefs } from 'pinia'
const featuredModpacks = ref({}) const featuredModpacks = ref({})
const featuredMods = ref({}) const featuredMods = ref({})
@ -19,18 +18,17 @@ const breadcrumbs = useBreadcrumbs()
breadcrumbs.setRootContext({ name: 'Home', link: route.path }) breadcrumbs.setRootContext({ name: 'Home', link: route.path })
const recentInstances = shallowRef([])
const offline = ref(await isOffline()) const offline = ref(await isOffline())
const getInstances = async () => { const instancesStore = useInstances()
const profiles = await list(true).catch(handleError) const { instancesByPlayed } = storeToRefs(instancesStore)
recentInstances.value = Object.values(profiles).sort((a, b) => {
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
})
const getInstances = async () => {
await instancesStore.refreshInstances()
// filter? TODO: Change this to be reactive along with fetching the rest.
let filters = [] let filters = []
for (const instance of recentInstances.value) { for (const instance of instancesByPlayed.value) {
if (instance.metadata.linked_data && instance.metadata.linked_data.project_id) { if (instance.metadata.linked_data && instance.metadata.linked_data.project_id) {
filters.push(`NOT"project_id"="${instance.metadata.linked_data.project_id}"`) filters.push(`NOT"project_id"="${instance.metadata.linked_data.project_id}"`)
} }
@ -84,7 +82,7 @@ const unlistenOffline = await offline_listener(async (b) => {
// computed sums of recentInstances, featuredModpacks, featuredMods, treating them as arrays if they are not // computed sums of recentInstances, featuredModpacks, featuredMods, treating them as arrays if they are not
const total = computed(() => { const total = computed(() => {
return ( return (
(recentInstances.value?.length ?? 0) + (instancesByPlayed.value?.length ?? 0) +
(featuredModpacks.value?.length ?? 0) + (featuredModpacks.value?.length ?? 0) +
(featuredMods.value?.length ?? 0) (featuredMods.value?.length ?? 0)
) )
@ -104,7 +102,7 @@ onUnmounted(() => {
{ {
label: 'Jump back in', label: 'Jump back in',
route: '/library', route: '/library',
instances: recentInstances, instances: instancesByPlayed,
downloaded: true, downloaded: true,
}, },
{ {

View File

@ -1,23 +1,23 @@
<script setup> <script setup>
import { onUnmounted, ref, shallowRef } from 'vue' import { onUnmounted, ref } from 'vue'
import GridDisplay from '@/components/GridDisplay.vue' 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'
import { offline_listener, profile_listener } from '@/helpers/events.js' import { offline_listener, profile_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
import { Button, PlusIcon } from 'omorphia' import { Button, PlusIcon } from 'omorphia'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { NewInstanceImage } from '@/assets/icons' import { NewInstanceImage } from '@/assets/icons'
import { isOffline } from '@/helpers/utils' import { isOffline } from '@/helpers/utils'
import { useInstances } from '@/store/instances'
import { storeToRefs } from 'pinia'
const route = useRoute() const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
breadcrumbs.setRootContext({ name: 'Library', link: route.path }) breadcrumbs.setRootContext({ name: 'Library', link: route.path })
const profiles = await list(true).catch(handleError) const instancesStore = useInstances()
const instances = shallowRef(Object.values(profiles)) const { instanceList } = storeToRefs(instancesStore)
const offline = ref(await isOffline()) const offline = ref(await isOffline())
const unlistenOffline = await offline_listener((b) => { const unlistenOffline = await offline_listener((b) => {
@ -25,9 +25,9 @@ const unlistenOffline = await offline_listener((b) => {
}) })
const unlistenProfile = await profile_listener(async () => { const unlistenProfile = await profile_listener(async () => {
const profiles = await list(true).catch(handleError) await instancesStore.refreshInstances()
instances.value = Object.values(profiles)
}) })
onUnmounted(() => { onUnmounted(() => {
unlistenProfile() unlistenProfile()
unlistenOffline() unlistenOffline()
@ -35,7 +35,7 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<GridDisplay v-if="instances.length > 0" label="Instances" :instances="instances" /> <GridDisplay v-if="instanceList.length > 0" label="Instances" :instances="instanceList" />
<div v-else class="no-instance"> <div v-else class="no-instance">
<div class="icon"> <div class="icon">
<NewInstanceImage /> <NewInstanceImage />

View File

@ -146,7 +146,7 @@ import {
} from '@/helpers/process' } from '@/helpers/process'
import { offline_listener, process_listener, profile_listener } from '@/helpers/events' import { offline_listener, process_listener, profile_listener } from '@/helpers/events'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ref, onUnmounted } from 'vue' import { ref, onUnmounted, defineProps, watch } from 'vue'
import { handleError, useBreadcrumbs, useLoading } from '@/store/state' import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
import { isOffline, showProfileInFolder } from '@/helpers/utils.js' import { isOffline, showProfileInFolder } from '@/helpers/utils.js'
import ContextMenu from '@/components/ui/ContextMenu.vue' import ContextMenu from '@/components/ui/ContextMenu.vue'
@ -154,12 +154,28 @@ import { mixpanel_track } from '@/helpers/mixpanel'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import { useFetch } from '@/helpers/fetch' import { useFetch } from '@/helpers/fetch'
const route = useRoute() const props = defineProps({
id: {
type: String,
required: false,
default: null,
},
})
const router = useRouter() const router = useRouter()
const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
const instance = ref(await get(route.params.id).catch(handleError)) const instance = ref(await get(route.params.id || props.id).catch(handleError))
watch(
() => route.params.id,
async (id) => {
if (!id) return
instance.value = await get(id).catch(handleError)
}
)
breadcrumbs.setName( breadcrumbs.setName(
'Instance', 'Instance',

View File

@ -440,10 +440,22 @@ const props = defineProps({
return false return false
}, },
}, },
playing: {
type: Boolean,
default() {
return false
},
},
versions: { versions: {
type: Array, type: Array,
required: true, required: true,
}, },
installed: {
type: Boolean,
default() {
return true
},
},
}) })
const projects = ref([]) const projects = ref([])

View File

@ -0,0 +1,43 @@
import { ref, onMounted, computed } from 'vue'
import dayjs from 'dayjs'
import { list } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications'
import { defineStore } from 'pinia'
export const useInstances = defineStore('instancesStore', () => {
const instances = ref({})
const instanceList = computed(() => {
return Object.values(instances.value)
})
const instancesByPlayed = computed(() => {
return instanceList.value.sort((a, b) => {
return dayjs(b?.metadata?.last_played ?? 0).diff(dayjs(a?.metadata?.last_played ?? 0))
})
})
const setInstances = async () => {
try {
const p = await list(true)
instances.value = p
} catch (error) {
handleError(error)
}
}
onMounted(async () => {
await setInstances()
})
const refreshInstances = async () => {
await setInstances()
}
return {
instanceList,
instancesByPlayed,
refreshInstances,
}
})