mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-04 02:20:19 +01:00
Add prompt to make Stirling your default PDF app
This commit is contained in:
@@ -2,13 +2,18 @@ import { ReactNode } from "react";
|
||||
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
||||
import { DesktopConfigSync } from '@app/components/DesktopConfigSync';
|
||||
import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
|
||||
import { DefaultAppPrompt } from '@app/components/DefaultAppPrompt';
|
||||
import { useDefaultAppPrompt } from '@app/hooks/useDefaultAppPrompt';
|
||||
|
||||
/**
|
||||
* 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 default PDF handler prompt on first launch
|
||||
*/
|
||||
export function AppProviders({ children }: { children: ReactNode }) {
|
||||
const { promptOpened, handleSetDefault, handleDismiss } = useDefaultAppPrompt();
|
||||
|
||||
return (
|
||||
<ProprietaryAppProviders
|
||||
appConfigRetryOptions={{
|
||||
@@ -22,6 +27,11 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
}}
|
||||
>
|
||||
<DesktopConfigSync />
|
||||
<DefaultAppPrompt
|
||||
opened={promptOpened}
|
||||
onSetDefault={handleSetDefault}
|
||||
onDismiss={handleDismiss}
|
||||
/>
|
||||
{children}
|
||||
</ProprietaryAppProviders>
|
||||
);
|
||||
|
||||
78
frontend/src/desktop/components/DefaultAppPrompt.tsx
Normal file
78
frontend/src/desktop/components/DefaultAppPrompt.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Modal, Text, Button, Stack, Flex } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { CSSProperties } from 'react';
|
||||
|
||||
interface DefaultAppPromptProps {
|
||||
opened: boolean;
|
||||
onSetDefault: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
const ICON_STYLE: CSSProperties = {
|
||||
fontSize: 48,
|
||||
display: 'block',
|
||||
margin: '0 auto 12px',
|
||||
color: 'var(--mantine-color-blue-6)',
|
||||
};
|
||||
|
||||
export const DefaultAppPrompt = ({ opened, onSetDefault, onDismiss }: DefaultAppPromptProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
opened={opened}
|
||||
onClose={onDismiss}
|
||||
title={t('defaultApp.title', 'Set as Default PDF App')}
|
||||
centered
|
||||
size="auto"
|
||||
closeOnClickOutside={true}
|
||||
closeOnEscape={true}
|
||||
>
|
||||
<Stack ta="center" p="md" gap="sm">
|
||||
<PictureAsPdfIcon style={ICON_STYLE} />
|
||||
<Text size="lg" fw={500}>
|
||||
{t(
|
||||
'defaultApp.message',
|
||||
'Would you like to set Stirling PDF as your default PDF editor?'
|
||||
)}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t(
|
||||
'defaultApp.description',
|
||||
'You can change this later in your system settings.'
|
||||
)}
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<Flex
|
||||
mt="md"
|
||||
gap="sm"
|
||||
justify="center"
|
||||
align="center"
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
>
|
||||
<Button
|
||||
variant="light"
|
||||
color="var(--mantine-color-gray-8)"
|
||||
onClick={onDismiss}
|
||||
leftSection={<CancelIcon fontSize="small" />}
|
||||
w="10rem"
|
||||
>
|
||||
{t('defaultApp.notNow', 'Not Now')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
color="var(--mantine-color-blue-9)"
|
||||
onClick={onSetDefault}
|
||||
leftSection={<CheckCircleOutlineIcon fontSize="small" />}
|
||||
w="10rem"
|
||||
>
|
||||
{t('defaultApp.setDefault', 'Set Default')}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
82
frontend/src/desktop/hooks/useDefaultAppPrompt.ts
Normal file
82
frontend/src/desktop/hooks/useDefaultAppPrompt.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { defaultAppService } from '@app/services/defaultAppService';
|
||||
import { alert } from '@app/components/toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function useDefaultAppPrompt() {
|
||||
const { t } = useTranslation();
|
||||
const [promptOpened, setPromptOpened] = useState(false);
|
||||
const [isSettingDefault, setIsSettingDefault] = useState(false);
|
||||
|
||||
// Check on mount if we should show the prompt
|
||||
useEffect(() => {
|
||||
const checkShouldPrompt = async () => {
|
||||
try {
|
||||
const shouldShow = await defaultAppService.shouldShowPrompt();
|
||||
if (shouldShow) {
|
||||
// Small delay so it doesn't show immediately on app launch
|
||||
setTimeout(() => setPromptOpened(true), 2000);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[DefaultAppPrompt] Failed to check prompt status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
checkShouldPrompt();
|
||||
}, []);
|
||||
|
||||
const handleSetDefault = async () => {
|
||||
setIsSettingDefault(true);
|
||||
try {
|
||||
const result = await defaultAppService.setAsDefaultPdfHandler();
|
||||
|
||||
if (result === 'set_successfully') {
|
||||
alert({
|
||||
alertType: 'success',
|
||||
title: t('defaultApp.success.title', 'Default App Set'),
|
||||
body: t(
|
||||
'defaultApp.success.message',
|
||||
'Stirling PDF is now your default PDF editor'
|
||||
),
|
||||
});
|
||||
} else if (result === 'opened_settings') {
|
||||
alert({
|
||||
alertType: 'neutral',
|
||||
title: t('defaultApp.settingsOpened.title', 'Settings Opened'),
|
||||
body: t(
|
||||
'defaultApp.settingsOpened.message',
|
||||
'Please select Stirling PDF in your system settings'
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as dismissed regardless of outcome
|
||||
defaultAppService.setPromptDismissed(true);
|
||||
setPromptOpened(false);
|
||||
} catch (error) {
|
||||
console.error('[DefaultAppPrompt] Failed to set default handler:', error);
|
||||
alert({
|
||||
alertType: 'error',
|
||||
title: t('defaultApp.error.title', 'Error'),
|
||||
body: t(
|
||||
'defaultApp.error.message',
|
||||
'Failed to set default PDF handler'
|
||||
),
|
||||
});
|
||||
} finally {
|
||||
setIsSettingDefault(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
defaultAppService.setPromptDismissed(true);
|
||||
setPromptOpened(false);
|
||||
};
|
||||
|
||||
return {
|
||||
promptOpened,
|
||||
isSettingDefault,
|
||||
handleSetDefault,
|
||||
handleDismiss,
|
||||
};
|
||||
}
|
||||
70
frontend/src/desktop/services/defaultAppService.ts
Normal file
70
frontend/src/desktop/services/defaultAppService.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
/**
|
||||
* Service for managing default PDF handler settings
|
||||
* Note: Uses localStorage for machine-specific preferences (not synced to server)
|
||||
*/
|
||||
export const defaultAppService = {
|
||||
/**
|
||||
* Check if Stirling PDF is the default PDF handler
|
||||
*/
|
||||
async isDefaultPdfHandler(): Promise<boolean> {
|
||||
try {
|
||||
const result = await invoke<boolean>('is_default_pdf_handler');
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('[DefaultApp] Failed to check default handler:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set or prompt to set Stirling PDF as default PDF handler
|
||||
* Returns a status string indicating what happened
|
||||
*/
|
||||
async setAsDefaultPdfHandler(): Promise<'set_successfully' | 'opened_settings' | 'error'> {
|
||||
try {
|
||||
const result = await invoke<string>('set_as_default_pdf_handler');
|
||||
return result as 'set_successfully' | 'opened_settings';
|
||||
} catch (error) {
|
||||
console.error('[DefaultApp] Failed to set default handler:', error);
|
||||
return 'error';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if user has dismissed the default app prompt (machine-specific)
|
||||
*/
|
||||
hasUserDismissedPrompt(): boolean {
|
||||
try {
|
||||
const dismissed = localStorage.getItem('stirlingpdf_default_app_prompt_dismissed');
|
||||
return dismissed === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark that user has dismissed the default app prompt (machine-specific)
|
||||
*/
|
||||
setPromptDismissed(dismissed: boolean): void {
|
||||
try {
|
||||
localStorage.setItem('stirlingpdf_default_app_prompt_dismissed', dismissed ? 'true' : 'false');
|
||||
} catch (error) {
|
||||
console.error('[DefaultApp] Failed to save prompt preference:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if we should show the default app prompt
|
||||
* Returns true if: user hasn't dismissed it AND app is not default handler
|
||||
*/
|
||||
async shouldShowPrompt(): Promise<boolean> {
|
||||
if (this.hasUserDismissedPrompt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isDefault = await this.isDefaultPdfHandler();
|
||||
return !isDefault;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user