Fix issues with opening files in desktop app (#4876)

# Description of Changes
Locking to just having one instance of the app running unifies the
experience across all OSs. Opening new files in Stirling will cause the
files to be opened in the existing window rather than spawning a new
instance of the app with just that file in the new instance.

There's much more to explore here to allow multiple windows open at
once, but that can be done all from one instance of the app, and will
likely make it easier to allow movement of files etc. across different
windows.

Also fixes extra newlines in the logs and directly builds to `.app` on
Mac because it's frustrating during development to have to repeatedly
mount & unmount the `.dmg`.
This commit is contained in:
James Brunton
2025-11-12 15:47:37 +00:00
committed by GitHub
parent a05c5a53c7
commit eb5f36aa15
10 changed files with 591 additions and 111 deletions
@@ -1,22 +1,22 @@
import { invoke, isTauri } from '@tauri-apps/api/core';
export interface FileOpenService {
getOpenedFile(): Promise<string | null>;
getOpenedFiles(): Promise<string[]>;
readFileAsArrayBuffer(filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null>;
clearOpenedFile(): Promise<void>;
clearOpenedFiles(): Promise<void>;
onFileOpened(callback: (filePath: string) => void): () => void; // Returns unlisten function
}
class TauriFileOpenService implements FileOpenService {
async getOpenedFile(): Promise<string | null> {
async getOpenedFiles(): Promise<string[]> {
try {
console.log('🔍 Calling invoke(get_opened_file)...');
const result = await invoke<string | null>('get_opened_file');
console.log('🔍 invoke(get_opened_file) returned:', result);
console.log('🔍 Calling invoke(get_opened_files)...');
const result = await invoke<string[]>('get_opened_files');
console.log('🔍 invoke(get_opened_files) returned:', result);
return result;
} catch (error) {
console.error('❌ Failed to get opened file:', error);
return null;
console.error('❌ Failed to get opened files:', error);
return [];
}
}
@@ -37,13 +37,13 @@ class TauriFileOpenService implements FileOpenService {
}
}
async clearOpenedFile(): Promise<void> {
async clearOpenedFiles(): Promise<void> {
try {
console.log('🔍 Calling invoke(clear_opened_file)...');
await invoke('clear_opened_file');
console.log('✅ Successfully cleared opened file');
console.log('🔍 Calling invoke(clear_opened_files)...');
await invoke('clear_opened_files');
console.log('✅ Successfully cleared opened files');
} catch (error) {
console.error('❌ Failed to clear opened file:', error);
console.error('❌ Failed to clear opened files:', error);
}
}
@@ -67,15 +67,9 @@ class TauriFileOpenService implements FileOpenService {
return;
}
// Listen for macOS native file open events
const unlistenMacOS = await listen('macos://open-file', (event) => {
console.log('📂 macOS native file open event:', event.payload);
callback(event.payload as string);
});
// Listen for fallback file open events
const unlistenFallback = await listen('file-opened', (event) => {
console.log('📂 Fallback file open event:', event.payload);
// Listen for unified file open events (all platforms)
const unlisten = await listen('file-opened', (event) => {
console.log('📂 File open event received:', event.payload);
callback(event.payload as string);
});
@@ -83,8 +77,7 @@ class TauriFileOpenService implements FileOpenService {
if (!isCleanedUp) {
cleanup = () => {
try {
unlistenMacOS();
unlistenFallback();
unlisten();
console.log('✅ File event listeners cleaned up');
} catch (error) {
console.error('❌ Error during file event cleanup:', error);
@@ -93,8 +86,7 @@ class TauriFileOpenService implements FileOpenService {
} else {
// Clean up immediately if cleanup was called during setup
try {
unlistenMacOS();
unlistenFallback();
unlisten();
} catch (error) {
console.error('❌ Error during immediate cleanup:', error);
}
@@ -118,9 +110,9 @@ class TauriFileOpenService implements FileOpenService {
}
class WebFileOpenService implements FileOpenService {
async getOpenedFile(): Promise<string | null> {
async getOpenedFiles(): Promise<string[]> {
// In web mode, there's no file association support
return null;
return [];
}
async readFileAsArrayBuffer(_filePath: string): Promise<{ fileName: string; arrayBuffer: ArrayBuffer } | null> {
@@ -128,7 +120,7 @@ class WebFileOpenService implements FileOpenService {
return null;
}
async clearOpenedFile(): Promise<void> {
async clearOpenedFiles(): Promise<void> {
// In web mode, no file clearing needed
}