fix: enhance loading states with skeletons and async state

This commit is contained in:
Johnpaul
2025-09-23 04:19:26 +01:00
parent e06f967803
commit 91eb417eba

View File

@@ -98,16 +98,9 @@
</template> </template>
<template #content> <template #content>
<!-- Loading State --> <!-- No Results State (only show when loaded and no results) -->
<div v-if="isLoading" class="flex items-center justify-center h-64">
<div class="text-neutral-500">
{{ $t('templateWorkflows.loading', 'Loading templates...') }}
</div>
</div>
<!-- No Results State -->
<div <div
v-else-if="filteredTemplates.length === 0 && !isLoading" v-if="!isLoading && filteredTemplates.length === 0"
class="flex flex-col items-center justify-center h-64 text-neutral-500" class="flex flex-col items-center justify-center h-64 text-neutral-500"
> >
<i-lucide:search class="w-12 h-12 mb-4 opacity-50" /> <i-lucide:search class="w-12 h-12 mb-4 opacity-50" />
@@ -126,8 +119,11 @@
<div v-else> <div v-else>
<!-- Title --> <!-- Title -->
<div class="px-6 pt-4 pb-2 text-2xl font-semibold text-neutral"> <div class="px-6 pt-4 pb-2 text-2xl font-semibold text-neutral">
<!-- show selected nav --> <span
<span> v-if="isLoading"
class="inline-block h-8 w-48 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
></span>
<span v-else>
{{ pageTitle }} {{ pageTitle }}
</span> </span>
</div> </div>
@@ -137,8 +133,40 @@
class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-x-4 gap-y-6 px-4 py-4" class="grid grid-cols-[repeat(auto-fill,minmax(16rem,1fr))] gap-x-4 gap-y-6 px-4 py-4"
data-testid="template-workflows-content" data-testid="template-workflows-content"
> >
<!-- Loading Skeletons (show while loading initial data) -->
<CardContainer <CardContainer
v-for="template in displayTemplates" v-for="n in isLoading ? 12 : 0"
:key="`initial-skeleton-${n}`"
ratio="square"
:max-width="300"
:min-width="200"
>
<template #top>
<CardTop ratio="landscape">
<template #default>
<div
class="w-full h-full bg-neutral-200 dark-theme:bg-neutral-700 animate-pulse"
></div>
</template>
</CardTop>
</template>
<template #bottom>
<CardBottom>
<div class="px-4 py-3">
<div
class="h-6 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse mb-2"
></div>
<div
class="h-4 bg-neutral-200 dark-theme:bg-neutral-700 rounded animate-pulse"
></div>
</div>
</CardBottom>
</template>
</CardContainer>
<!-- Actual Template Cards -->
<CardContainer
v-for="template in isLoading ? [] : displayTemplates"
:key="template.name" :key="template.name"
ref="cardRefs" ref="cardRefs"
v-memo="[template.name, hoveredTemplate === template.name]" v-memo="[template.name, hoveredTemplate === template.name]"
@@ -355,8 +383,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useAsyncState } from '@vueuse/core'
import ProgressSpinner from 'primevue/progressspinner' import ProgressSpinner from 'primevue/progressspinner'
import { computed, onBeforeUnmount, onMounted, provide, ref, watch } from 'vue' import { computed, onBeforeUnmount, provide, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import IconTextButton from '@/components/button/IconTextButton.vue' import IconTextButton from '@/components/button/IconTextButton.vue'
@@ -420,8 +449,36 @@ const openTutorial = (template: TemplateInfo) => {
} }
} }
// Get navigation items from the store // Get navigation items from the store, with skeleton items while loading
const navItems = computed<(NavItemData | NavGroupData)[]>(() => { const navItems = computed<(NavItemData | NavGroupData)[]>(() => {
// Show skeleton navigation items while loading
if (isLoading.value) {
return [
{
id: 'skeleton-all',
label: 'All Templates',
icon: 'icon-[lucide--layout-grid]'
},
{
id: 'skeleton-basics',
label: 'Basics',
icon: 'icon-[lucide--graduation-cap]'
},
{
title: 'Generation Type',
items: [
{ id: 'skeleton-1', label: '...', icon: 'icon-[lucide--loader-2]' },
{ id: 'skeleton-2', label: '...', icon: 'icon-[lucide--loader-2]' }
]
},
{
title: 'Closed Source Models',
items: [
{ id: 'skeleton-3', label: '...', icon: 'icon-[lucide--loader-2]' }
]
}
]
}
return workflowTemplatesStore.navGroupedTemplates return workflowTemplatesStore.navGroupedTemplates
}) })
@@ -490,7 +547,6 @@ const selectedLicenseObjects = computed({
}) })
// Loading states // Loading states
const isLoading = ref(true)
const loadingTemplate = ref<string | null>(null) const loadingTemplate = ref<string | null>(null)
const hoveredTemplate = ref<string | null>(null) const hoveredTemplate = ref<string | null>(null)
const cardRefs = ref<HTMLElement[]>([]) const cardRefs = ref<HTMLElement[]>([])
@@ -663,12 +719,22 @@ const pageTitle = computed(() => {
t('templateWorkflows.allTemplates', 'All Templates') t('templateWorkflows.allTemplates', 'All Templates')
}) })
// Initialize // Initialize templates loading with useAsyncState
onMounted(async () => { const { isLoading } = useAsyncState(
await loadTemplates() async () => {
await workflowTemplatesStore.loadWorkflowTemplates() // Run both operations in parallel for better performance
isLoading.value = false await Promise.all([
}) loadTemplates(),
workflowTemplatesStore.loadWorkflowTemplates()
])
return true
},
false, // initial state
{
immediate: true // Start loading immediately
}
)
onBeforeUnmount(() => { onBeforeUnmount(() => {
cardRefs.value = [] // Release DOM refs cardRefs.value = [] // Release DOM refs
}) })