diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 749a53c1..04555488 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -18,6 +18,7 @@ import Users from './pages/Users'; import LogosPage from './pages/Logos'; import VODsPage from './pages/VODs'; import useAuthStore from './store/auth'; +import useLogosStore from './store/logos'; import FloatingVideo from './components/FloatingVideo'; import { WebsocketProvider } from './WebSocket'; import { Box, AppShell, MantineProvider } from '@mantine/core'; @@ -38,6 +39,8 @@ const defaultRoute = '/channels'; const App = () => { const [open, setOpen] = useState(true); + const [backgroundLoadingStarted, setBackgroundLoadingStarted] = + useState(false); const isAuthenticated = useAuthStore((s) => s.isAuthenticated); const setIsAuthenticated = useAuthStore((s) => s.setIsAuthenticated); const logout = useAuthStore((s) => s.logout); @@ -77,6 +80,11 @@ const App = () => { const loggedIn = await initializeAuth(); if (loggedIn) { await initData(); + // Start background logo loading after app is fully initialized (only once) + if (!backgroundLoadingStarted) { + setBackgroundLoadingStarted(true); + useLogosStore.getState().startBackgroundLoading(); + } } else { await logout(); } @@ -87,7 +95,7 @@ const App = () => { }; checkAuth(); - }, [initializeAuth, initData, logout]); + }, [initializeAuth, initData, logout, backgroundLoadingStarted]); return ( ({ localStorage.setItem('refreshToken', response.refresh); localStorage.setItem('tokenExpiration', expiration); - // Start background loading of channel-assignable logos - setTimeout(() => { - try { - //useLogosStore.getState().backgroundLoadChannelLogos(); - useLogosStore.getState().fetchAllLogos(); - } catch (error) { - console.log('Background logo loading not available:', error); - } - }, 1000); // Wait 1 second after login to start background loading + // Don't start background loading here - let it happen after app initialization } } catch (error) { console.error('Login failed:', error); diff --git a/frontend/src/store/logos.jsx b/frontend/src/store/logos.jsx index 3fa143cb..4a0b945c 100644 --- a/frontend/src/store/logos.jsx +++ b/frontend/src/store/logos.jsx @@ -68,23 +68,32 @@ const useLogosStore = create((set, get) => ({ }, fetchAllLogos: async () => { + const { isLoading, hasLoadedAll, logos } = get(); + + // Prevent unnecessary reloading if we already have all logos + if (isLoading || (hasLoadedAll && Object.keys(logos).length > 0)) { + return Object.values(logos); + } + set({ isLoading: true, error: null }); try { // Disable pagination to get all logos for management interface const response = await api.getLogos({ no_pagination: 'true' }); // Handle both paginated and non-paginated responses - const logos = Array.isArray(response) ? response : response.results || []; + const logosArray = Array.isArray(response) + ? response + : response.results || []; set({ - logos: logos.reduce((acc, logo) => { + logos: logosArray.reduce((acc, logo) => { acc[logo.id] = { ...logo }; return acc; }, {}), hasLoadedAll: true, // Mark that we've loaded all logos isLoading: false, }); - return logos; + return logosArray; } catch (error) { console.error('Failed to fetch all logos:', error); set({ error: 'Failed to load all logos.', isLoading: false }); @@ -234,6 +243,54 @@ const useLogosStore = create((set, get) => ({ } }, + // Background loading specifically for all logos after login + backgroundLoadAllLogos: async () => { + const { backgroundLoading, hasLoadedAll } = get(); + + // Don't start if already loading or if we already have all logos loaded + if (backgroundLoading || hasLoadedAll) { + return; + } + + set({ backgroundLoading: true }); + + // Use setTimeout to make this truly non-blocking + setTimeout(async () => { + try { + // Use the API directly to avoid interfering with the main isLoading state + const response = await api.getLogos({ no_pagination: 'true' }); + const logosArray = Array.isArray(response) + ? response + : response.results || []; + + // Process logos in smaller chunks to avoid blocking the main thread + const chunkSize = 1000; + const logoObject = {}; + + for (let i = 0; i < logosArray.length; i += chunkSize) { + const chunk = logosArray.slice(i, i + chunkSize); + chunk.forEach((logo) => { + logoObject[logo.id] = { ...logo }; + }); + + // Yield control back to the main thread between chunks + if (i + chunkSize < logosArray.length) { + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + + set({ + logos: logoObject, + hasLoadedAll: true, + backgroundLoading: false, + }); + } catch (error) { + console.error('Background all logos loading failed:', error); + set({ backgroundLoading: false }); + } + }, 0); // Execute immediately but asynchronously + }, + // Background loading specifically for channel-assignable logos after login backgroundLoadChannelLogos: async () => { const { backgroundLoading, channelLogos, hasLoadedChannelLogos } = get(); @@ -262,6 +319,19 @@ const useLogosStore = create((set, get) => ({ } }, + // Start background loading after app is fully initialized + startBackgroundLoading: () => { + // Use a longer delay to ensure app is fully loaded + setTimeout(() => { + // Fire and forget - don't await this + get() + .backgroundLoadAllLogos() + .catch((error) => { + console.error('Background logo loading failed:', error); + }); + }, 3000); // Wait 3 seconds after app initialization + }, + // Helper methods getLogoById: (logoId) => { return get().logos[logoId] || null;