split from page components
This commit is contained in:
parent
a48186fa63
commit
694ee7e89f
@ -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 {
|
||||||
|
|||||||
12
theseus_gui/src/helpers/icon.js
Normal file
12
theseus_gui/src/helpers/icon.js
Normal 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)
|
||||||
|
}
|
||||||
@ -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,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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 />
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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([])
|
||||||
|
|||||||
43
theseus_gui/src/store/instances.js
Normal file
43
theseus_gui/src/store/instances.js
Normal 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,
|
||||||
|
}
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user