mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Add prompt to make Stirling your default PDF app
This commit is contained in:
parent
50760b5302
commit
cb2446ae83
@ -41,6 +41,25 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"confirm": "Extract"
|
"confirm": "Extract"
|
||||||
},
|
},
|
||||||
|
"defaultApp": {
|
||||||
|
"title": "Set as Default PDF App",
|
||||||
|
"message": "Would you like to set Stirling PDF as your default PDF editor?",
|
||||||
|
"description": "You can change this later in your system settings.",
|
||||||
|
"notNow": "Not Now",
|
||||||
|
"setDefault": "Set Default",
|
||||||
|
"success": {
|
||||||
|
"title": "Default App Set",
|
||||||
|
"message": "Stirling PDF is now your default PDF editor"
|
||||||
|
},
|
||||||
|
"settingsOpened": {
|
||||||
|
"title": "Settings Opened",
|
||||||
|
"message": "Please select Stirling PDF in your system settings"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error",
|
||||||
|
"message": "Failed to set default PDF handler"
|
||||||
|
}
|
||||||
|
},
|
||||||
"language": {
|
"language": {
|
||||||
"direction": "ltr"
|
"direction": "ltr"
|
||||||
},
|
},
|
||||||
|
|||||||
11
frontend/src-tauri/Cargo.lock
generated
11
frontend/src-tauri/Cargo.lock
generated
@ -643,6 +643,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-services"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0aa845ab21b847ee46954be761815f18f16469b29ef3ba250241b1b8bab659a"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -3941,6 +3950,8 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|||||||
name = "stirling-pdf"
|
name = "stirling-pdf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"core-foundation 0.10.1",
|
||||||
|
"core-services",
|
||||||
"log",
|
"log",
|
||||||
"reqwest 0.11.27",
|
"reqwest 0.11.27",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@ -31,3 +31,7 @@ tauri-plugin-fs = "2.4.4"
|
|||||||
tauri-plugin-single-instance = "2.0.1"
|
tauri-plugin-single-instance = "2.0.1"
|
||||||
tokio = { version = "1.0", features = ["time"] }
|
tokio = { version = "1.0", features = ["time"] }
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
core-foundation = "0.10"
|
||||||
|
core-services = "1.0"
|
||||||
|
|||||||
212
frontend/src-tauri/src/commands/default_app.rs
Normal file
212
frontend/src-tauri/src/commands/default_app.rs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
use crate::utils::add_log;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
/// Check if Stirling PDF is the default PDF handler
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn is_default_pdf_handler() -> Result<bool, String> {
|
||||||
|
add_log("🔍 Checking if app is default PDF handler".to_string());
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
check_default_windows()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
check_default_macos()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
check_default_linux()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to set/prompt for Stirling PDF as default PDF handler
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn set_as_default_pdf_handler() -> Result<String, String> {
|
||||||
|
add_log("⚙️ Attempting to set as default PDF handler".to_string());
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
set_default_windows()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
set_default_macos()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
set_default_linux()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Windows Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn check_default_windows() -> Result<bool, String> {
|
||||||
|
// Query the default handler for .pdf extension
|
||||||
|
let output = Command::new("cmd")
|
||||||
|
.args(["/C", "assoc .pdf"])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to check default app: {}", e))?;
|
||||||
|
|
||||||
|
let assoc = String::from_utf8_lossy(&output.stdout);
|
||||||
|
add_log(format!("Windows PDF association: {}", assoc.trim()));
|
||||||
|
|
||||||
|
// Get the ProgID for .pdf files
|
||||||
|
if let Some(prog_id) = assoc.trim().strip_prefix(".pdf=") {
|
||||||
|
// Query what application handles this ProgID
|
||||||
|
let output = Command::new("cmd")
|
||||||
|
.args(["/C", &format!("ftype {}", prog_id)])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to query file type: {}", e))?;
|
||||||
|
|
||||||
|
let ftype = String::from_utf8_lossy(&output.stdout);
|
||||||
|
add_log(format!("Windows file type: {}", ftype.trim()));
|
||||||
|
|
||||||
|
// Check if it contains "Stirling" or our app name
|
||||||
|
let is_default = ftype.to_lowercase().contains("stirling");
|
||||||
|
Ok(is_default)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn set_default_windows() -> Result<String, String> {
|
||||||
|
// On Windows 10+, we need to open the Default Apps settings
|
||||||
|
// as programmatic setting requires a signed installer
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(["/C", "start", "ms-settings:defaultapps"])
|
||||||
|
.spawn()
|
||||||
|
.map_err(|e| format!("Failed to open default apps settings: {}", e))?;
|
||||||
|
|
||||||
|
add_log("Opened Windows Default Apps settings".to_string());
|
||||||
|
Ok("opened_settings".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// macOS Implementation (using LaunchServices framework)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn check_default_macos() -> Result<bool, String> {
|
||||||
|
use core_foundation::base::TCFType;
|
||||||
|
use core_foundation::string::{CFString, CFStringRef};
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
// Define the LSCopyDefaultRoleHandlerForContentType function
|
||||||
|
#[link(name = "CoreServices", kind = "framework")]
|
||||||
|
extern "C" {
|
||||||
|
fn LSCopyDefaultRoleHandlerForContentType(
|
||||||
|
content_type: CFStringRef,
|
||||||
|
role: c_int,
|
||||||
|
) -> CFStringRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
const K_LS_ROLES_ALL: c_int = 0xFFFFFFFF_u32 as c_int;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Query the default handler for "com.adobe.pdf" (PDF UTI - standard macOS identifier)
|
||||||
|
let pdf_uti = CFString::new("com.adobe.pdf");
|
||||||
|
let handler_ref = LSCopyDefaultRoleHandlerForContentType(pdf_uti.as_concrete_TypeRef(), K_LS_ROLES_ALL);
|
||||||
|
|
||||||
|
if handler_ref.is_null() {
|
||||||
|
add_log("No default PDF handler found".to_string());
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let handler = CFString::wrap_under_create_rule(handler_ref);
|
||||||
|
let handler_str = handler.to_string();
|
||||||
|
add_log(format!("macOS PDF handler: {}", handler_str));
|
||||||
|
|
||||||
|
// Check if it's our bundle identifier
|
||||||
|
let is_default = handler_str == "stirling.pdf.dev";
|
||||||
|
Ok(is_default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn set_default_macos() -> Result<String, String> {
|
||||||
|
use core_foundation::base::TCFType;
|
||||||
|
use core_foundation::string::{CFString, CFStringRef};
|
||||||
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
// Define the LSSetDefaultRoleHandlerForContentType function
|
||||||
|
#[link(name = "CoreServices", kind = "framework")]
|
||||||
|
extern "C" {
|
||||||
|
fn LSSetDefaultRoleHandlerForContentType(
|
||||||
|
content_type: CFStringRef,
|
||||||
|
role: c_int,
|
||||||
|
handler_bundle_id: CFStringRef,
|
||||||
|
) -> c_int; // OSStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
const K_LS_ROLES_ALL: c_int = 0xFFFFFFFF_u32 as c_int;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Set our app as the default handler for PDF files
|
||||||
|
let pdf_uti = CFString::new("com.adobe.pdf");
|
||||||
|
let our_bundle_id = CFString::new("stirling.pdf.dev");
|
||||||
|
|
||||||
|
let status = LSSetDefaultRoleHandlerForContentType(
|
||||||
|
pdf_uti.as_concrete_TypeRef(),
|
||||||
|
K_LS_ROLES_ALL,
|
||||||
|
our_bundle_id.as_concrete_TypeRef(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if status == 0 {
|
||||||
|
add_log("Successfully triggered default app dialog".to_string());
|
||||||
|
Ok("set_successfully".to_string())
|
||||||
|
} else {
|
||||||
|
let error_msg = format!("LaunchServices returned status: {}", status);
|
||||||
|
add_log(error_msg.clone());
|
||||||
|
Err(error_msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Linux Implementation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn check_default_linux() -> Result<bool, String> {
|
||||||
|
// Use xdg-mime to check the default application for PDF files
|
||||||
|
let output = Command::new("xdg-mime")
|
||||||
|
.args(["query", "default", "application/pdf"])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to check default app: {}", e))?;
|
||||||
|
|
||||||
|
let handler = String::from_utf8_lossy(&output.stdout);
|
||||||
|
add_log(format!("Linux PDF handler: {}", handler.trim()));
|
||||||
|
|
||||||
|
// Check if it's our .desktop file
|
||||||
|
let is_default = handler.trim() == "stirling-pdf.desktop";
|
||||||
|
Ok(is_default)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn set_default_linux() -> Result<String, String> {
|
||||||
|
// Use xdg-mime to set the default application for PDF files
|
||||||
|
let result = Command::new("xdg-mime")
|
||||||
|
.args(["default", "stirling-pdf.desktop", "application/pdf"])
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("Failed to set default app: {}", e))?;
|
||||||
|
|
||||||
|
if result.status.success() {
|
||||||
|
add_log("Set as default PDF handler on Linux".to_string());
|
||||||
|
Ok("set_successfully".to_string())
|
||||||
|
} else {
|
||||||
|
let error = String::from_utf8_lossy(&result.stderr);
|
||||||
|
add_log(format!("Failed to set default: {}", error));
|
||||||
|
Err(format!("Failed to set as default: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod health;
|
pub mod health;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
|
pub mod default_app;
|
||||||
|
|
||||||
pub use backend::{start_backend, cleanup_backend};
|
pub use backend::{start_backend, cleanup_backend};
|
||||||
pub use health::check_backend_health;
|
pub use health::check_backend_health;
|
||||||
pub use files::{get_opened_files, clear_opened_files, add_opened_file};
|
pub use files::{get_opened_files, clear_opened_files, add_opened_file};
|
||||||
|
pub use default_app::{is_default_pdf_handler, set_as_default_pdf_handler};
|
||||||
|
|||||||
@ -3,7 +3,16 @@ use tauri::{RunEvent, WindowEvent, Emitter, Manager};
|
|||||||
mod utils;
|
mod utils;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
|
||||||
use commands::{start_backend, check_backend_health, get_opened_files, clear_opened_files, cleanup_backend, add_opened_file};
|
use commands::{
|
||||||
|
start_backend,
|
||||||
|
check_backend_health,
|
||||||
|
get_opened_files,
|
||||||
|
clear_opened_files,
|
||||||
|
cleanup_backend,
|
||||||
|
add_opened_file,
|
||||||
|
is_default_pdf_handler,
|
||||||
|
set_as_default_pdf_handler,
|
||||||
|
};
|
||||||
use utils::{add_log, get_tauri_logs};
|
use utils::{add_log, get_tauri_logs};
|
||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
@ -39,7 +48,15 @@ pub fn run() {
|
|||||||
add_log("🔍 DEBUG: Setup completed".to_string());
|
add_log("🔍 DEBUG: Setup completed".to_string());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![start_backend, check_backend_health, get_opened_files, clear_opened_files, get_tauri_logs])
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
start_backend,
|
||||||
|
check_backend_health,
|
||||||
|
get_opened_files,
|
||||||
|
clear_opened_files,
|
||||||
|
get_tauri_logs,
|
||||||
|
is_default_pdf_handler,
|
||||||
|
set_as_default_pdf_handler,
|
||||||
|
])
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
.expect("error while building tauri application")
|
.expect("error while building tauri application")
|
||||||
.run(|app_handle, event| {
|
.run(|app_handle, event| {
|
||||||
|
|||||||
@ -2,13 +2,18 @@ import { ReactNode } from "react";
|
|||||||
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
|
||||||
import { DesktopConfigSync } from '@app/components/DesktopConfigSync';
|
import { DesktopConfigSync } from '@app/components/DesktopConfigSync';
|
||||||
import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
|
import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
|
||||||
|
import { DefaultAppPrompt } from '@app/components/DefaultAppPrompt';
|
||||||
|
import { useDefaultAppPrompt } from '@app/hooks/useDefaultAppPrompt';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Desktop application providers
|
* Desktop application providers
|
||||||
* Wraps proprietary providers and adds desktop-specific configuration
|
* Wraps proprietary providers and adds desktop-specific configuration
|
||||||
* - Enables retry logic for app config (needed for Tauri mode when backend is starting)
|
* - 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 }) {
|
export function AppProviders({ children }: { children: ReactNode }) {
|
||||||
|
const { promptOpened, handleSetDefault, handleDismiss } = useDefaultAppPrompt();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProprietaryAppProviders
|
<ProprietaryAppProviders
|
||||||
appConfigRetryOptions={{
|
appConfigRetryOptions={{
|
||||||
@ -22,6 +27,11 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DesktopConfigSync />
|
<DesktopConfigSync />
|
||||||
|
<DefaultAppPrompt
|
||||||
|
opened={promptOpened}
|
||||||
|
onSetDefault={handleSetDefault}
|
||||||
|
onDismiss={handleDismiss}
|
||||||
|
/>
|
||||||
{children}
|
{children}
|
||||||
</ProprietaryAppProviders>
|
</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;
|
||||||
|
},
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user