mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-02 21:03:34 +03:00
# Summary - Adds desktop file tracking: local paths are preserved and save buttons now work as expcted (doing Save/Save As as appropriate) - Adds logic to track whether files are 'dirty' (they've been modified by some tool, and not saved to disk yet). - Improves file state UX (dirty vs saved) and close warnings - Web behaviour should be unaffected by these changes ## Indicators Files now have indicators in desktop mode to tell you their state. ### File up-to-date with disk <img width="318" height="393" alt="image" src="https://github.com/user-attachments/assets/06325f9a-afd7-4c2f-8a5b-6d11e3093115" /> ### File modified by a tool but not saved to disk yet <img width="357" height="385" alt="image" src="https://github.com/user-attachments/assets/1a7716d9-c6f7-4d13-be0d-c1de6493954b" /> ### File not tracked on disk <img width="312" height="379" alt="image" src="https://github.com/user-attachments/assets/9cffe300-bd9a-4e19-97c7-9b98bebefacc" /> # Limitations - It's a bit weird that we still have files stored in indexeddb in the app, which are still loadable. We might want to change this behaviour in the future - Viewer's Save doesn't persist to disk. I've left that out here because it'd need a lot of testing to make sure the logic's right with making sure you can leave the Viewer with applying the changes to the PDF _without_ saving to disk - There's no current way to do Save As on a file that has already been persisted to disk - it's only ever Save. Similarly, there's no way to duplicate a file. --------- Co-authored-by: James Brunton <jbrunton96@gmail.com> Co-authored-by: James Brunton <james@stirlingpdf.com>
158 lines
5.0 KiB
TypeScript
158 lines
5.0 KiB
TypeScript
import { ReactNode, useEffect, useState } from "react";
|
|
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
|
import { DesktopConfigSync } from '@app/components/DesktopConfigSync';
|
|
import { DesktopBannerInitializer } from '@app/components/DesktopBannerInitializer';
|
|
import { SaveShortcutListener } from '@app/components/SaveShortcutListener';
|
|
import { SetupWizard } from '@app/components/SetupWizard';
|
|
import { useFirstLaunchCheck } from '@app/hooks/useFirstLaunchCheck';
|
|
import { useBackendInitializer } from '@app/hooks/useBackendInitializer';
|
|
import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
|
|
import { connectionModeService } from '@app/services/connectionModeService';
|
|
import { tauriBackendService } from '@app/services/tauriBackendService';
|
|
import { authService } from '@app/services/authService';
|
|
import { getCurrentWindow } from '@tauri-apps/api/window';
|
|
import { isTauri } from '@tauri-apps/api/core';
|
|
|
|
/**
|
|
* Desktop application providers
|
|
* Wraps proprietary providers and adds desktop-specific configuration
|
|
* - Enables retry logic for app config (needed for Tauri mode when backend is starting)
|
|
* - Shows setup wizard on first launch
|
|
*/
|
|
export function AppProviders({ children }: { children: ReactNode }) {
|
|
const { isFirstLaunch, setupComplete } = useFirstLaunchCheck();
|
|
const [connectionMode, setConnectionMode] = useState<'saas' | 'selfhosted' | null>(null);
|
|
const [authChecked, setAuthChecked] = useState(false);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
// Load connection mode on mount
|
|
useEffect(() => {
|
|
void connectionModeService.getCurrentMode().then(setConnectionMode);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!isFirstLaunch && setupComplete) {
|
|
authService.isAuthenticated()
|
|
.then(setIsAuthenticated)
|
|
.catch(() => setIsAuthenticated(false))
|
|
.finally(() => setAuthChecked(true));
|
|
} else if (isFirstLaunch && !setupComplete) {
|
|
setAuthChecked(true);
|
|
setIsAuthenticated(false);
|
|
}
|
|
}, [isFirstLaunch, setupComplete]);
|
|
|
|
// Initialize backend health monitoring for self-hosted mode
|
|
useEffect(() => {
|
|
if (setupComplete && !isFirstLaunch && connectionMode === 'selfhosted') {
|
|
void tauriBackendService.initializeExternalBackend();
|
|
}
|
|
}, [setupComplete, isFirstLaunch, connectionMode]);
|
|
|
|
// Initialize monitoring for bundled backend (already started in Rust)
|
|
// This sets up port detection and health checks
|
|
const shouldMonitorBackend = setupComplete && !isFirstLaunch && connectionMode === 'saas';
|
|
useBackendInitializer(shouldMonitorBackend);
|
|
|
|
useEffect(() => {
|
|
if (!authChecked) {
|
|
return;
|
|
}
|
|
|
|
if (!isTauri()) {
|
|
return;
|
|
}
|
|
|
|
const currentWindow = getCurrentWindow();
|
|
currentWindow
|
|
.show()
|
|
.then(() => currentWindow.unminimize().catch(() => {}))
|
|
.then(() => currentWindow.setFocus().catch(() => {}))
|
|
.then(() => currentWindow.requestUserAttention(1).catch(() => {}))
|
|
.catch(() => {});
|
|
}, [authChecked]);
|
|
|
|
if (!authChecked) {
|
|
return (
|
|
<ProprietaryAppProviders
|
|
appConfigRetryOptions={{
|
|
maxRetries: 5,
|
|
initialDelay: 1000,
|
|
}}
|
|
appConfigProviderProps={{
|
|
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
|
|
bootstrapMode: 'non-blocking',
|
|
autoFetch: false,
|
|
}}
|
|
>
|
|
<div style={{ minHeight: '100vh' }} />
|
|
</ProprietaryAppProviders>
|
|
);
|
|
}
|
|
|
|
// Show setup wizard on first launch
|
|
if (isFirstLaunch && !setupComplete) {
|
|
return (
|
|
<ProprietaryAppProviders
|
|
appConfigRetryOptions={{
|
|
maxRetries: 5,
|
|
initialDelay: 1000,
|
|
}}
|
|
appConfigProviderProps={{
|
|
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
|
|
bootstrapMode: 'non-blocking',
|
|
autoFetch: false,
|
|
}}
|
|
>
|
|
<SetupWizard
|
|
onComplete={() => {
|
|
window.location.reload();
|
|
}}
|
|
/>
|
|
</ProprietaryAppProviders>
|
|
);
|
|
}
|
|
|
|
// Show setup wizard when not authenticated (desktop login flow).
|
|
if (authChecked && !isAuthenticated) {
|
|
return (
|
|
<ProprietaryAppProviders
|
|
appConfigRetryOptions={{
|
|
maxRetries: 5,
|
|
initialDelay: 1000,
|
|
}}
|
|
appConfigProviderProps={{
|
|
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
|
|
bootstrapMode: 'non-blocking',
|
|
autoFetch: false,
|
|
}}
|
|
>
|
|
<SetupWizard
|
|
onComplete={() => {
|
|
window.location.reload();
|
|
}}
|
|
/>
|
|
</ProprietaryAppProviders>
|
|
);
|
|
}
|
|
|
|
// Normal app flow
|
|
return (
|
|
<ProprietaryAppProviders
|
|
appConfigRetryOptions={{
|
|
maxRetries: 5,
|
|
initialDelay: 1000, // 1 second, with exponential backoff
|
|
}}
|
|
appConfigProviderProps={{
|
|
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
|
|
bootstrapMode: 'non-blocking',
|
|
autoFetch: false,
|
|
}}
|
|
>
|
|
<DesktopConfigSync />
|
|
<DesktopBannerInitializer />
|
|
<SaveShortcutListener />
|
|
{children}
|
|
</ProprietaryAppProviders>
|
|
);
|
|
}
|