Implement loading
This commit is contained in:
@@ -1,35 +1,29 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon } from 'omorphia'
|
||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, AnimatedLogo } 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'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
onMounted(async () => {
|
||||
const { theme } = await get()
|
||||
themeStore.setThemeState(theme)
|
||||
})
|
||||
const loading = ref(true)
|
||||
|
||||
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
|
||||
defineExpose({
|
||||
initialize: async () => {
|
||||
loading.value = false
|
||||
const { theme } = await get()
|
||||
themeStore.setThemeState(theme)
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div v-if="!loading" class="container">
|
||||
<div class="nav-container">
|
||||
<div class="nav-section">
|
||||
<suspense>
|
||||
@@ -74,6 +68,9 @@ list().then(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="loading-page">
|
||||
<AnimatedLogo class="initializing-icon" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -316,4 +313,23 @@ list().then(
|
||||
height: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.loading-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #16181c;
|
||||
height: 100vh;
|
||||
|
||||
.initializing-icon {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
|
||||
:deep(svg) {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { RouterLink } from 'vue-router'
|
||||
import { Card } from 'omorphia'
|
||||
import { AnimatedLogo, Card } from 'omorphia'
|
||||
import { PlayIcon } from '@/assets/icons'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
|
||||
@@ -25,7 +25,10 @@ const props = defineProps({
|
||||
{{ props.instance.metadata.loader }} {{ props.instance.metadata.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="cta"><PlayIcon /></div>
|
||||
<div class="cta" :class="{ loading: !instance.installed }">
|
||||
<PlayIcon v-if="instance.installed" />
|
||||
<AnimatedLogo v-else class="loading-icon" />
|
||||
</div>
|
||||
</Card>
|
||||
</RouterLink>
|
||||
</div>
|
||||
@@ -63,11 +66,27 @@ const props = defineProps({
|
||||
opacity: 0;
|
||||
transition: 0.3s ease-in-out bottom, 0.1s ease-in-out opacity;
|
||||
cursor: pointer;
|
||||
|
||||
&.loading {
|
||||
background: var(--color-button-bg);
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
|
||||
:deep(svg) {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: brightness(0.75);
|
||||
box-shadow: var(--shadow-floating);
|
||||
|
||||
@@ -10,26 +10,59 @@
|
||||
<Button icon-only class="icon-button" @click="goToTerminal()">
|
||||
<TerminalIcon />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="currentLoadingBars.length > 0"
|
||||
ref="infoButton"
|
||||
icon-only
|
||||
class="icon-button show-card-icon"
|
||||
@click="toggleCard()"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="status">
|
||||
<span class="circle stopped" />
|
||||
<span class="running-text"> No running profiles </span>
|
||||
<Button
|
||||
v-if="currentLoadingBars.length > 0"
|
||||
ref="infoButton"
|
||||
icon-only
|
||||
class="icon-button show-card-icon"
|
||||
@click="toggleCard()"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Card v-if="showCard === true" ref="card" class="info-card">
|
||||
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="loading-option">
|
||||
<AnimatedLogo class="loading-icon" />
|
||||
<div class="loading-text">
|
||||
<div class="row">
|
||||
{{ loadingBar.bar_type.PackDownload.pack_name }}
|
||||
</div>
|
||||
<div class="row">{{ Math.round(loadingBar.current) }} % {{ loadingBar.message }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button } from 'omorphia'
|
||||
import { Button, DownloadIcon, Card, AnimatedLogo } from 'omorphia'
|
||||
import { StopIcon, TerminalIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import {
|
||||
get_all_running_profiles as getRunningProfiles,
|
||||
kill_by_uuid as killProfile,
|
||||
get_uuids_by_profile_path as getProfileProcesses,
|
||||
} from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
const infoButton = ref(null)
|
||||
const showCard = ref(false)
|
||||
|
||||
const currentProcesses = ref(await getRunningProfiles())
|
||||
|
||||
@@ -55,6 +88,52 @@ const stop = async () => {
|
||||
const goToTerminal = () => {
|
||||
router.push(`/instance/${encodeURIComponent(currentProcesses.value[0].path)}/logs`)
|
||||
}
|
||||
|
||||
const currentLoadingBars = ref(
|
||||
Object.values(await progress_bars_list()).filter(
|
||||
(bar) => bar.bar_type !== 'StateInit' && Object.keys(bar.bar_type)[0] === 'PackDownload'
|
||||
)
|
||||
)
|
||||
|
||||
await loading_listener(async (event) => {
|
||||
console.log('loading listener', event.message, event)
|
||||
await refreshInfo()
|
||||
})
|
||||
|
||||
const refreshInfo = async () => {
|
||||
currentLoadingBars.value = Object.values(await progress_bars_list()).filter(
|
||||
(bar) => bar.bar_type !== 'StateInit' && Object.keys(bar.bar_type)[0] === 'PackDownload'
|
||||
)
|
||||
if (currentLoadingBars.value.length === 0) {
|
||||
showCard.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleClickOutside = (event) => {
|
||||
console.log('clicked outside from appbar')
|
||||
if (
|
||||
card.value &&
|
||||
infoButton.value.$el !== event.target &&
|
||||
card.value.$el !== event.target &&
|
||||
!document.elementsFromPoint(event.clientX, event.clientY).includes(card.value.$el) &&
|
||||
!document.elementsFromPoint(event.clientX, event.clientY).includes(infoButton.value.$el)
|
||||
) {
|
||||
showCard.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCard = async () => {
|
||||
showCard.value = !showCard.value
|
||||
await refreshInfo()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -100,4 +179,68 @@ const goToTerminal = () => {
|
||||
--text-color: var(--color-red) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.info-card {
|
||||
position: absolute;
|
||||
top: 3.25rem;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
width: 20rem;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-raised);
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overflow: auto;
|
||||
transition: all 0.2s ease-in-out;
|
||||
border: 1px solid var(--color-button-bg);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
:hover {
|
||||
background-color: var(--color-raised-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: block;
|
||||
|
||||
:deep(svg) {
|
||||
left: 1rem;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.show-card-icon {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -16,8 +16,10 @@ app.use(pinia)
|
||||
app.use(FloatingVue)
|
||||
app.mixin(loadCssMixin)
|
||||
|
||||
const mountedApp = app.mount('#app')
|
||||
|
||||
initialize_state()
|
||||
.then(() => app.mount('#app'))
|
||||
.then(() => mountedApp.initialize())
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
@@ -4,14 +4,21 @@ import { shallowRef } from 'vue'
|
||||
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'
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const profiles = await list()
|
||||
const instances = shallowRef(Object.values(profiles))
|
||||
const instances = shallowRef(Object.values(await list()))
|
||||
const loadingInstances = shallowRef(Object.values(await progress_bars_list()))
|
||||
|
||||
breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
||||
loading_listener(async (profile) => {
|
||||
console.log(profile)
|
||||
instances.value = Object.values(await list())
|
||||
loadingInstances.value = Object.values(await progress_bars_list())
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -10,9 +10,15 @@
|
||||
</span>
|
||||
</div>
|
||||
<span class="button-group">
|
||||
<Button color="primary" class="instance-button" @click="run($route.params.id)">
|
||||
<PlayIcon />
|
||||
Play
|
||||
<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 />
|
||||
@@ -41,13 +47,23 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { BoxIcon, SettingsIcon, FileIcon, Button, Avatar, Card, Promotion } from 'omorphia'
|
||||
import {
|
||||
BoxIcon,
|
||||
SettingsIcon,
|
||||
FileIcon,
|
||||
Button,
|
||||
Avatar,
|
||||
Card,
|
||||
Promotion,
|
||||
AnimatedLogo,
|
||||
} from 'omorphia'
|
||||
import { PlayIcon, OpenFolderIcon } from '@/assets/icons'
|
||||
import { get, run } from '@/helpers/profile'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { shallowRef } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -58,6 +74,12 @@ breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
link: route.path,
|
||||
})
|
||||
|
||||
profile_listener(async (event) => {
|
||||
if (event.profile_path === route.params.id) {
|
||||
instance.value = await get(route.params.id)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -274,4 +296,17 @@ Button {
|
||||
height: 1px;
|
||||
margin: var(--gap-xl) 0;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
animation: spin 1s linear infinite;
|
||||
|
||||
:deep(svg) {
|
||||
left: 2rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user