V2 Tauri integration (#3854)

# Description of Changes

Please provide a summary of the changes, including:

## Add PDF File Association Support for Tauri App

  ### 🎯 **Features Added**
  - PDF file association configuration in Tauri
  - Command line argument detection for opened files
  - Automatic file loading when app is launched via "Open with"
  - Cross-platform support (Windows/macOS)

  ### 🔧 **Technical Changes**
  - Added `fileAssociations` in `tauri.conf.json` for PDF files
  - New `get_opened_file` Tauri command to detect file arguments
  - `fileOpenService` with Tauri fs plugin integration
  - `useOpenedFile` hook for React integration
  - Improved backend health logging during startup (reduced noise)

  ### 🧪 **Testing**
See 
* https://v2.tauri.app/start/prerequisites/
*
[DesktopApplicationDevelopmentGuide.md](DesktopApplicationDevelopmentGuide.md)

  ```bash
  # Test file association during development:
  
  cd frontend
  npm install
  cargo tauri dev --no-watch -- -- "path/to/file.pdf"
  ```

 For production testing:
  1. Build: npm run tauri build
  2. Install the built app
  3. Right-click PDF → "Open with" → Stirling-PDF

  🚀 User Experience

- Users can now double-click PDF files to open them directly in
Stirling-PDF
- Files automatically load in the viewer when opened via file
association
  - Seamless integration with OS file handling

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.

---------

Co-authored-by: Connor Yoh <[email protected]>
Co-authored-by: James Brunton <[email protected]>
Co-authored-by: James Brunton <[email protected]>
This commit is contained in:
ConnorYoh
2025-11-05 11:44:59 +00:00
committed by GitHub
co-authored by Connor Yoh James Brunton James Brunton
parent f3eed4428d
commit 4c0c9b28ef
120 changed files with 11005 additions and 1294 deletions
@@ -0,0 +1,135 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import apiClient from '@app/services/apiClient';
// Retry configuration
const MAX_RETRIES = 5;
const INITIAL_DELAY = 1000; // 1 second
/**
* Sleep utility for delays
*/
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
export interface AppConfig {
baseUrl?: string;
contextPath?: string;
serverPort?: number;
appNameNavbar?: string;
languages?: string[];
enableLogin?: boolean;
enableEmailInvites?: boolean;
isAdmin?: boolean;
enableAlphaFunctionality?: boolean;
enableAnalytics?: boolean | null;
enablePosthog?: boolean | null;
enableScarf?: boolean | null;
premiumEnabled?: boolean;
premiumKey?: string;
termsAndConditions?: string;
privacyPolicy?: string;
cookiePolicy?: string;
impressum?: string;
accessibilityStatement?: string;
runningProOrHigher?: boolean;
runningEE?: boolean;
license?: string;
SSOAutoLogin?: boolean;
serverCertificateEnabled?: boolean;
error?: string;
}
interface AppConfigContextValue {
config: AppConfig | null;
loading: boolean;
error: string | null;
refetch: () => Promise<void>;
}
// Create context
const AppConfigContext = createContext<AppConfigContextValue | undefined>(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: React.ReactNode }> = ({ children }) => {
const [config, setConfig] = useState<AppConfig | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchConfig = async () => {
setLoading(true);
setError(null);
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
if (attempt > 0) {
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
console.log(`[AppConfig] Retry attempt ${attempt}/${MAX_RETRIES} after ${delay}ms delay...`);
await sleep(delay);
} else {
console.log('[AppConfig] Fetching app config...');
}
const response = await apiClient.get<AppConfig>('/api/v1/config/app-config');
setConfig(response.data);
console.log('[AppConfig] Successfully fetched app config');
setLoading(false);
return; // Success - exit function
} catch (err: any) {
const status = err?.response?.status;
// Check if we should retry (network errors or 5xx errors)
const shouldRetry = (!status || status >= 500) && attempt < MAX_RETRIES;
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);
break;
}
}
setLoading(false);
};
useEffect(() => {
fetchConfig();
}, []);
const value: AppConfigContextValue = {
config,
loading,
error,
refetch: fetchConfig,
};
return (
<AppConfigContext.Provider value={value}>
{children}
</AppConfigContext.Provider>
);
};
/**
* Hook to access application configuration
* Must be used within AppConfigProvider
*/
export function useAppConfig(): AppConfigContextValue {
const context = useContext(AppConfigContext);
if (context === undefined) {
throw new Error('useAppConfig must be used within AppConfigProvider');
}
return context;
}