mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-07-27 07:35:22 +00:00
Open With Stirling-Pdf
This commit is contained in:
parent
ed618648e0
commit
780bd663bb
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"@mui/material": "^7.1.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
@ -2020,9 +2021,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/api": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.5.0.tgz",
|
||||
"integrity": "sha512-Ldux4ip+HGAcPUmuLT8EIkk6yafl5vK0P0c0byzAKzxJh7vxelVtdPONjfgTm96PbN24yjZNESY8CKo8qniluA==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.6.0.tgz",
|
||||
"integrity": "sha512-hRNcdercfgpzgFrMXWwNDBN0B7vNzOzRepy6ZAmhxi5mDLVPNrTpo9MGg2tN/F7JRugj4d2aF7E1rtPXAHaetg==",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@ -2246,6 +2247,15 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-fs": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.0.tgz",
|
||||
"integrity": "sha512-Sp8AdDcbyXyk6LD6Pmdx44SH3LPeNAvxR2TFfq/8CwqzfO1yOyV+RzT8fov0NNN7d9nvW7O7MtMAptJ42YXA5g==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"@mui/material": "^7.1.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
|
23
frontend/src-tauri/Cargo.lock
generated
23
frontend/src-tauri/Cargo.lock
generated
@ -100,6 +100,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-shell",
|
||||
"tokio",
|
||||
@ -3871,6 +3872,28 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-fs"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ead0daec5d305adcefe05af9d970fc437bcc7996052d564e7393eb291252da"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dunce",
|
||||
"glob",
|
||||
"percent-encoding",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.12",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-log"
|
||||
version = "2.4.0"
|
||||
|
@ -24,5 +24,6 @@ log = "0.4"
|
||||
tauri = { version = "2.5.0", features = [] }
|
||||
tauri-plugin-log = "2.0.0-rc"
|
||||
tauri-plugin-shell = "2.1.0"
|
||||
tauri-plugin-fs = "2.0.0"
|
||||
tokio = { version = "1.0", features = ["time"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
@ -6,6 +6,10 @@
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default"
|
||||
"core:default",
|
||||
{
|
||||
"identifier": "fs:allow-read-file",
|
||||
"allow": [{ "path": "**" }]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -318,6 +318,23 @@ async fn check_backend_health() -> Result<bool, String> {
|
||||
|
||||
|
||||
|
||||
// Command to get opened file path (if app was launched with a file)
|
||||
#[tauri::command]
|
||||
async fn get_opened_file() -> Result<Option<String>, String> {
|
||||
// Get command line arguments
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
// Look for a PDF file argument (skip the first arg which is the executable)
|
||||
for arg in args.iter().skip(1) {
|
||||
if arg.ends_with(".pdf") && std::path::Path::new(arg).exists() {
|
||||
add_log(format!("📂 PDF file opened: {}", arg));
|
||||
return Ok(Some(arg.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
// Command to check bundled runtime and JAR
|
||||
#[tauri::command]
|
||||
async fn check_jar_exists(app: tauri::AppHandle) -> Result<String, String> {
|
||||
@ -405,6 +422,7 @@ fn cleanup_backend() {
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.setup(|app| {
|
||||
|
||||
// Automatically start the backend when Tauri starts
|
||||
@ -424,7 +442,7 @@ pub fn run() {
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
.invoke_handler(tauri::generate_handler![start_backend, check_backend_health, check_jar_exists])
|
||||
.invoke_handler(tauri::generate_handler![start_backend, check_backend_health, check_jar_exists, get_opened_file])
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application")
|
||||
.run(|app_handle, event| {
|
||||
|
@ -32,12 +32,22 @@
|
||||
"resources": [
|
||||
"libs/*.jar",
|
||||
"runtime/jre/**/*"
|
||||
],
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": ["pdf"],
|
||||
"name": "PDF Document",
|
||||
"description": "Open PDF files with Stirling-PDF",
|
||||
"role": "Editor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"plugins": {
|
||||
"shell": {
|
||||
"open": true
|
||||
},
|
||||
"fs": {
|
||||
"requireLiteralLeadingDot": false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { RainbowThemeProvider } from './components/shared/RainbowThemeProvider';
|
||||
import HomePage from './pages/HomePage';
|
||||
import { useOpenedFile } from './hooks/useOpenedFile';
|
||||
|
||||
// Import global styles
|
||||
import './styles/tailwind.css';
|
||||
@ -9,6 +10,7 @@ import './index.css';
|
||||
import { BackendHealthIndicator } from './components/BackendHealthIndicator';
|
||||
|
||||
export default function App() {
|
||||
const { openedFilePath, loading: fileLoading } = useOpenedFile();
|
||||
useEffect(() => {
|
||||
// Only start backend if running in Tauri
|
||||
const initializeBackend = async () => {
|
||||
@ -39,7 +41,7 @@ export default function App() {
|
||||
</div>
|
||||
</div>
|
||||
<RainbowThemeProvider>
|
||||
<HomePage />
|
||||
<HomePage openedFilePath={openedFilePath} />
|
||||
</RainbowThemeProvider>
|
||||
</div>
|
||||
);
|
||||
|
@ -50,6 +50,15 @@ export const useBackendHealth = (checkInterval: number = 2000) => {
|
||||
});
|
||||
|
||||
if (isHealthy) {
|
||||
// Log success message if this is the first successful check after failures
|
||||
if (attemptCount > 0) {
|
||||
const now = new Date();
|
||||
const timeSinceStartup = now.getTime() - startupTime.getTime();
|
||||
console.log('✅ Backend health check successful:', {
|
||||
timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's',
|
||||
attemptsBeforeSuccess: attemptCount,
|
||||
});
|
||||
}
|
||||
setAttemptCount(0); // Reset attempt count on success
|
||||
}
|
||||
} catch (error) {
|
||||
@ -71,6 +80,24 @@ export const useBackendHealth = (checkInterval: number = 2000) => {
|
||||
errorMessage = isWithinStartupPeriod ? 'Backend starting up...' : 'Health check failed';
|
||||
}
|
||||
|
||||
// Only log errors to console after startup period
|
||||
if (!isWithinStartupPeriod) {
|
||||
console.error('Backend health check failed:', {
|
||||
error: error instanceof Error ? error.message : error,
|
||||
timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's',
|
||||
attemptCount,
|
||||
});
|
||||
} else {
|
||||
// During startup, only log on first few attempts to reduce noise
|
||||
if (attemptCount <= 3) {
|
||||
console.log('Backend health check (startup period):', {
|
||||
message: errorMessage,
|
||||
timeSinceStartup: Math.round(timeSinceStartup / 1000) + 's',
|
||||
attempt: attemptCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setHealthState({
|
||||
isHealthy: false,
|
||||
isChecking: false,
|
||||
|
29
frontend/src/hooks/useOpenedFile.ts
Normal file
29
frontend/src/hooks/useOpenedFile.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { fileOpenService } from '../services/fileOpenService';
|
||||
|
||||
export function useOpenedFile() {
|
||||
const [openedFilePath, setOpenedFilePath] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const checkForOpenedFile = async () => {
|
||||
try {
|
||||
const filePath = await fileOpenService.getOpenedFile();
|
||||
|
||||
if (filePath) {
|
||||
console.log('✅ App opened with file:', filePath);
|
||||
setOpenedFilePath(filePath);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to check for opened file:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkForOpenedFile();
|
||||
}, []);
|
||||
|
||||
return { openedFilePath, loading };
|
||||
}
|
@ -51,7 +51,11 @@ const toolEndpoints: Record<string, string[]> = {
|
||||
merge: ["merge-pdfs"],
|
||||
};
|
||||
|
||||
export default function HomePage() {
|
||||
interface HomePageProps {
|
||||
openedFilePath?: string | null;
|
||||
}
|
||||
|
||||
export default function HomePage({ openedFilePath }: HomePageProps) {
|
||||
const { t } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
const theme = useMantineTheme();
|
||||
@ -122,6 +126,7 @@ export default function HomePage() {
|
||||
restoreActiveFiles();
|
||||
}, []);
|
||||
|
||||
|
||||
// Helper function to check if a tool is available
|
||||
const isToolAvailable = (toolKey: string): boolean => {
|
||||
if (endpointsLoading) return true; // Show tools while loading
|
||||
@ -311,6 +316,41 @@ export default function HomePage() {
|
||||
}
|
||||
}, [handleViewChange, setActiveFiles]);
|
||||
|
||||
// Handle opened file from command line arguments
|
||||
useEffect(() => {
|
||||
if (openedFilePath) {
|
||||
const loadOpenedFile = async () => {
|
||||
try {
|
||||
console.log('Loading opened file:', openedFilePath);
|
||||
|
||||
// Use the file open service to read the file
|
||||
const { fileOpenService } = await import('../services/fileOpenService');
|
||||
const fileData = await fileOpenService.readFileAsArrayBuffer(openedFilePath);
|
||||
|
||||
if (!fileData) {
|
||||
throw new Error('Failed to read file data');
|
||||
}
|
||||
|
||||
// Create a File object directly from ArrayBuffer
|
||||
const file = new File([fileData.arrayBuffer], fileData.fileName, {
|
||||
type: 'application/pdf',
|
||||
lastModified: Date.now()
|
||||
});
|
||||
|
||||
// Add to active files and switch to viewer
|
||||
addToActiveFiles(file);
|
||||
setCurrentView('viewer');
|
||||
|
||||
console.log('Successfully loaded opened file:', fileData.fileName);
|
||||
} catch (error) {
|
||||
console.error('Failed to load opened file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadOpenedFile();
|
||||
}
|
||||
}, [openedFilePath, addToActiveFiles, setCurrentView]);
|
||||
|
||||
const selectedTool = toolRegistry[selectedToolKey];
|
||||
|
||||
// For Viewer - convert first active file to expected format (only when needed)
|
||||
|
53
frontend/src/services/fileOpenService.ts
Normal file
53
frontend/src/services/fileOpenService.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
export interface FileOpenService {
|
||||
getOpenedFile(): Promise<string | null>;
|
||||
readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null>;
|
||||
}
|
||||
|
||||
class TauriFileOpenService implements FileOpenService {
|
||||
async getOpenedFile(): Promise<string | null> {
|
||||
try {
|
||||
const result = await invoke<string | null>('get_opened_file');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Failed to get opened file:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null> {
|
||||
try {
|
||||
const { readFile } = await import('@tauri-apps/plugin-fs');
|
||||
|
||||
const fileData = await readFile(filePath);
|
||||
const fileName = filePath.split(/[\\/]/).pop() || 'opened-file.pdf';
|
||||
|
||||
return {
|
||||
fileName,
|
||||
arrayBuffer: fileData.buffer.slice(fileData.byteOffset, fileData.byteOffset + fileData.byteLength)
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to read file:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WebFileOpenService implements FileOpenService {
|
||||
async getOpenedFile(): Promise<string | null> {
|
||||
// In web mode, there's no file association support
|
||||
return null;
|
||||
}
|
||||
|
||||
async readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null> {
|
||||
// In web mode, cannot read arbitrary file paths
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Export the appropriate service based on environment
|
||||
export const fileOpenService: FileOpenService =
|
||||
typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)
|
||||
? new TauriFileOpenService()
|
||||
: new WebFileOpenService();
|
Loading…
x
Reference in New Issue
Block a user