mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-13 02:18:16 +01:00
Tidy Tauri code and enable "Open file in Stirling PDF" (#4836)
# Description of Changes Tidy Tauri code and enable "Open file in Stirling PDF"
This commit is contained in:
@@ -8,7 +8,7 @@ import { ToolWorkflowProvider } from "@app/contexts/ToolWorkflowContext";
|
||||
import { HotkeyProvider } from "@app/contexts/HotkeyContext";
|
||||
import { SidebarProvider } from "@app/contexts/SidebarContext";
|
||||
import { PreferencesProvider } from "@app/contexts/PreferencesContext";
|
||||
import { AppConfigProvider } from "@app/contexts/AppConfigContext";
|
||||
import { AppConfigProvider, AppConfigRetryOptions } from "@app/contexts/AppConfigContext";
|
||||
import { RightRailProvider } from "@app/contexts/RightRailContext";
|
||||
import { ViewerProvider } from "@app/contexts/ViewerContext";
|
||||
import { SignatureProvider } from "@app/contexts/SignatureContext";
|
||||
@@ -16,6 +16,7 @@ import { OnboardingProvider } from "@app/contexts/OnboardingContext";
|
||||
import { TourOrchestrationProvider } from "@app/contexts/TourOrchestrationContext";
|
||||
import ErrorBoundary from "@app/components/shared/ErrorBoundary";
|
||||
import { useScarfTracking } from "@app/hooks/useScarfTracking";
|
||||
import { useAppInitialization } from "@app/hooks/useAppInitialization";
|
||||
|
||||
// Component to initialize scarf tracking (must be inside AppConfigProvider)
|
||||
function ScarfTrackingInitializer() {
|
||||
@@ -23,19 +24,31 @@ function ScarfTrackingInitializer() {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Component to run app-level initialization (must be inside AppProviders for context access)
|
||||
function AppInitializer() {
|
||||
useAppInitialization();
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface AppProvidersProps {
|
||||
children: ReactNode;
|
||||
appConfigRetryOptions?: AppConfigRetryOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Core application providers
|
||||
* Contains all providers needed for the core
|
||||
*/
|
||||
export function AppProviders({ children }: { children: ReactNode }) {
|
||||
export function AppProviders({ children, appConfigRetryOptions }: AppProvidersProps) {
|
||||
return (
|
||||
<PreferencesProvider>
|
||||
<RainbowThemeProvider>
|
||||
<ErrorBoundary>
|
||||
<OnboardingProvider>
|
||||
<AppConfigProvider>
|
||||
<AppConfigProvider retryOptions={appConfigRetryOptions}>
|
||||
<ScarfTrackingInitializer />
|
||||
<FileContextProvider enableUrlSync={true} enablePersistence={true}>
|
||||
<AppInitializer />
|
||||
<ToolRegistryProvider>
|
||||
<NavigationProvider>
|
||||
<FilesModalProvider>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
/**
|
||||
* Sleep utility for delays
|
||||
*/
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export interface AppConfigRetryOptions {
|
||||
maxRetries?: number;
|
||||
initialDelay?: number;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
baseUrl?: string;
|
||||
contextPath?: string;
|
||||
@@ -47,12 +59,18 @@ const AppConfigContext = createContext<AppConfigContextValue | undefined>({
|
||||
* Provider component that fetches and provides app configuration
|
||||
* Should be placed at the top level of the app, before any components that need config
|
||||
*/
|
||||
export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
export const AppConfigProvider: React.FC<{
|
||||
children: ReactNode;
|
||||
retryOptions?: AppConfigRetryOptions;
|
||||
}> = ({ children, retryOptions }) => {
|
||||
const [config, setConfig] = useState<AppConfig | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [fetchCount, setFetchCount] = useState(0);
|
||||
|
||||
const maxRetries = retryOptions?.maxRetries ?? 0;
|
||||
const initialDelay = retryOptions?.initialDelay ?? 1000;
|
||||
|
||||
const fetchConfig = async (force = false) => {
|
||||
// Prevent duplicate fetches unless forced
|
||||
if (!force && fetchCount > 0) {
|
||||
@@ -60,35 +78,59 @@ export const AppConfigProvider: React.FC<{ children: ReactNode }> = ({ children
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
// apiClient automatically adds JWT header if available via interceptors
|
||||
const response = await apiClient.get<AppConfig>('/api/v1/config/app-config');
|
||||
const data = response.data;
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
if (attempt > 0) {
|
||||
const delay = initialDelay * Math.pow(2, attempt - 1);
|
||||
console.log(`[AppConfig] Retry attempt ${attempt}/${maxRetries} after ${delay}ms delay...`);
|
||||
await sleep(delay);
|
||||
} else {
|
||||
console.log('[AppConfig] Fetching app config...');
|
||||
}
|
||||
|
||||
console.debug('[AppConfig] Config fetched successfully:', data);
|
||||
setConfig(data);
|
||||
setFetchCount(prev => prev + 1);
|
||||
} catch (err: any) {
|
||||
// On 401 (not authenticated), use default config with login enabled
|
||||
// This allows the app to work even without authentication
|
||||
if (err.response?.status === 401) {
|
||||
console.debug('[AppConfig] 401 error - using default config (login enabled)');
|
||||
setConfig({ enableLogin: true });
|
||||
// apiClient automatically adds JWT header if available via interceptors
|
||||
const response = await apiClient.get<AppConfig>('/api/v1/config/app-config');
|
||||
const data = response.data;
|
||||
|
||||
console.debug('[AppConfig] Config fetched successfully:', data);
|
||||
setConfig(data);
|
||||
setFetchCount(prev => prev + 1);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
return; // Success - exit function
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status;
|
||||
|
||||
const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
|
||||
setError(errorMessage);
|
||||
console.error('[AppConfig] Failed to fetch app config:', err);
|
||||
// On error, assume login is enabled (safe default)
|
||||
setConfig({ enableLogin: true });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
// On 401 (not authenticated), use default config with login enabled
|
||||
// This allows the app to work even without authentication
|
||||
if (status === 401) {
|
||||
console.debug('[AppConfig] 401 error - using default config (login enabled)');
|
||||
setConfig({ enableLogin: true });
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should retry (network errors or 5xx errors)
|
||||
const shouldRetry = (!status || status >= 500) && attempt < maxRetries;
|
||||
|
||||
if (shouldRetry) {
|
||||
console.warn(`[AppConfig] Attempt ${attempt + 1} failed (status ${status || 'network error'}):`, err.message, '- will retry...');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Final attempt failed or non-retryable error (4xx)
|
||||
const errorMessage = err?.response?.data?.message || err?.message || 'Unknown error occurred';
|
||||
setError(errorMessage);
|
||||
console.error(`[AppConfig] Failed to fetch app config after ${attempt + 1} attempts:`, err);
|
||||
// On error, assume login is enabled (safe default)
|
||||
setConfig({ enableLogin: true });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
10
frontend/src/core/hooks/useAppInitialization.ts
Normal file
10
frontend/src/core/hooks/useAppInitialization.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* App initialization hook
|
||||
* Core version: no initialization needed
|
||||
*
|
||||
* This hook is called once when the app starts to allow different builds
|
||||
* to perform initialization tasks that require access to contexts like FileContext.
|
||||
*/
|
||||
export function useAppInitialization(): void {
|
||||
// Core version has no initialization
|
||||
}
|
||||
@@ -20,21 +20,13 @@ import { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
||||
import AppConfigModal from "@app/components/shared/AppConfigModal";
|
||||
import ToolPanelModePrompt from "@app/components/tools/ToolPanelModePrompt";
|
||||
import AdminAnalyticsChoiceModal from "@app/components/shared/AdminAnalyticsChoiceModal";
|
||||
import { useHomePageExtensions } from "@app/pages/useHomePageExtensions";
|
||||
|
||||
import "@app/pages/HomePage.css";
|
||||
|
||||
type MobileView = "tools" | "workbench";
|
||||
|
||||
interface HomePageProps {
|
||||
openedFile?: File | null;
|
||||
}
|
||||
|
||||
export default function HomePage({ openedFile }: HomePageProps = {}) {
|
||||
export default function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Extension hook for desktop-specific behavior (e.g., file opening)
|
||||
useHomePageExtensions(openedFile);
|
||||
const {
|
||||
sidebarRefs,
|
||||
} = useSidebarContext();
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Extension point for HomePage behaviour.
|
||||
* Core version does nothing.
|
||||
*/
|
||||
export function useHomePageExtensions(_openedFile?: File | null) {
|
||||
useEffect(() => {
|
||||
}, [_openedFile]);
|
||||
}
|
||||
Reference in New Issue
Block a user