22 * Tauri API wrapper module
33 * Provides a unified interface for Tauri 2.x APIs
44 * Uses the global __TAURI__ object instead of ES module imports
5+ * Falls back to Web APIs when running in a browser (non-Tauri)
56 */
67
8+ // ── Web file cache ──────────────────────────────────────────────────────────
9+ // When running in a browser, files opened via <input type="file"> are stored
10+ // here so that readBinaryFile() can retrieve them by name.
11+ const _webFileCache = new Map ( ) ; // filename -> Uint8Array
12+
713// Extract a display-friendly file name from a path or content:// URI
814export function extractFileName ( pathOrUri ) {
915 if ( ! pathOrUri ) return 'Document' ;
@@ -95,47 +101,79 @@ export async function closeWindow() {
95101}
96102
97103// File dialogs - using Tauri commands since plugin APIs may not be globally available
98- export async function openFileDialog ( ) {
99- if ( ! isTauri ( ) ) return null ;
100-
101- // Try using the dialog plugin via window.__TAURI__.dialog
102- if ( window . __TAURI__ . dialog ) {
103- try {
104- const result = await window . __TAURI__ . dialog . open ( {
105- multiple : false ,
106- filters : [ { name : 'PDF Files' , extensions : [ 'pdf' ] } ]
107- } ) ;
108- return result ;
109- } catch ( e ) {
110- console . error ( 'Dialog plugin error:' , e ) ;
104+ export async function openFileDialog ( extensions ) {
105+ if ( isTauri ( ) ) {
106+ const filters = extensions
107+ ? [ { name : 'Files' , extensions } ]
108+ : [ { name : 'PDF Files' , extensions : [ 'pdf' ] } ] ;
109+
110+ // Try using the dialog plugin via window.__TAURI__.dialog
111+ if ( window . __TAURI__ . dialog ) {
112+ try {
113+ const result = await window . __TAURI__ . dialog . open ( {
114+ multiple : false ,
115+ filters
116+ } ) ;
117+ return result ;
118+ } catch ( e ) {
119+ console . error ( 'Dialog plugin error:' , e ) ;
120+ }
111121 }
122+
123+ // Fallback: use invoke to call a custom command
124+ return await invoke ( 'open_file_dialog' ) ;
112125 }
113126
114- // Fallback: use invoke to call a custom command
115- return await invoke ( 'open_file_dialog' ) ;
127+ // Web fallback: use HTML <input type="file">
128+ const accept = extensions
129+ ? extensions . map ( e => '.' + e ) . join ( ',' )
130+ : '.pdf' ;
131+ return new Promise ( ( resolve ) => {
132+ const input = document . createElement ( 'input' ) ;
133+ input . type = 'file' ;
134+ input . accept = accept ;
135+ input . style . display = 'none' ;
136+ input . addEventListener ( 'change' , async ( ) => {
137+ const file = input . files ?. [ 0 ] ;
138+ document . body . removeChild ( input ) ;
139+ if ( ! file ) { resolve ( null ) ; return ; }
140+ const data = new Uint8Array ( await file . arrayBuffer ( ) ) ;
141+ _webFileCache . set ( file . name , data ) ;
142+ resolve ( file . name ) ;
143+ } ) ;
144+ input . addEventListener ( 'cancel' , ( ) => {
145+ document . body . removeChild ( input ) ;
146+ resolve ( null ) ;
147+ } ) ;
148+ document . body . appendChild ( input ) ;
149+ input . click ( ) ;
150+ } ) ;
116151}
117152
118153export async function saveFileDialog ( defaultPath , filters ) {
119- if ( ! isTauri ( ) ) return null ;
120-
121- if ( ! filters ) {
122- filters = [ { name : 'PDF Files' , extensions : [ 'pdf' ] } ] ;
123- }
154+ if ( isTauri ( ) ) {
155+ if ( ! filters ) {
156+ filters = [ { name : 'PDF Files' , extensions : [ 'pdf' ] } ] ;
157+ }
124158
125- // Try using the dialog plugin
126- if ( window . __TAURI__ . dialog ) {
127- try {
128- const result = await window . __TAURI__ . dialog . save ( {
129- defaultPath : defaultPath ,
130- filters : filters
131- } ) ;
132- return result ;
133- } catch ( e ) {
134- console . error ( 'Dialog plugin error:' , e ) ;
159+ // Try using the dialog plugin
160+ if ( window . __TAURI__ . dialog ) {
161+ try {
162+ const result = await window . __TAURI__ . dialog . save ( {
163+ defaultPath : defaultPath ,
164+ filters : filters
165+ } ) ;
166+ return result ;
167+ } catch ( e ) {
168+ console . error ( 'Dialog plugin error:' , e ) ;
169+ }
135170 }
171+
172+ return null ;
136173 }
137174
138- return null ;
175+ // Web fallback: return the suggested filename (writeBinaryFile will trigger download)
176+ return defaultPath || 'document.pdf' ;
139177}
140178
141179// Folder picker dialog
@@ -160,7 +198,12 @@ export async function openFolderDialog(title) {
160198
161199// File system operations
162200export async function readBinaryFile ( path ) {
163- if ( ! isTauri ( ) ) return null ;
201+ // Web fallback: check the in-memory file cache first
202+ if ( ! isTauri ( ) ) {
203+ const cached = _webFileCache . get ( path ) ;
204+ if ( cached ) return cached ;
205+ return null ;
206+ }
164207
165208 // Use the fs plugin directly
166209 if ( window . __TAURI__ . fs ) {
@@ -171,7 +214,26 @@ export async function readBinaryFile(path) {
171214}
172215
173216export async function writeBinaryFile ( path , data ) {
174- if ( ! isTauri ( ) ) return false ;
217+ if ( ! isTauri ( ) ) {
218+ // Web fallback: trigger a browser download
219+ const fileName = path . replace ( / ^ .* [ \\ / ] / , '' ) || 'download' ;
220+ const ext = fileName . split ( '.' ) . pop ( ) ?. toLowerCase ( ) ;
221+ const mimeMap = {
222+ pdf : 'application/pdf' , png : 'image/png' , jpg : 'image/jpeg' ,
223+ jpeg : 'image/jpeg' , csv : 'text/csv' , xfdf : 'application/xml' ,
224+ xml : 'application/xml' ,
225+ } ;
226+ const blob = new Blob ( [ data ] , { type : mimeMap [ ext ] || 'application/octet-stream' } ) ;
227+ const url = URL . createObjectURL ( blob ) ;
228+ const a = document . createElement ( 'a' ) ;
229+ a . href = url ;
230+ a . download = fileName ;
231+ document . body . appendChild ( a ) ;
232+ a . click ( ) ;
233+ document . body . removeChild ( a ) ;
234+ URL . revokeObjectURL ( url ) ;
235+ return true ;
236+ }
175237
176238 // Use the fs plugin directly - no fallback to slow base64 method
177239 if ( window . __TAURI__ . fs ) {
@@ -322,7 +384,9 @@ export async function buildUserAgent() {
322384
323385// Get app version from Tauri config
324386export async function getAppVersion ( ) {
325- if ( ! isTauri ( ) ) return null ;
387+ if ( ! isTauri ( ) ) {
388+ return typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : null ;
389+ }
326390 try {
327391 return await window . __TAURI__ . app . getVersion ( ) ;
328392 } catch {
@@ -346,10 +410,20 @@ export async function getOpenedFiles() {
346410
347411// Session management
348412export async function saveSession ( data ) {
413+ if ( ! isTauri ( ) ) {
414+ try { localStorage . setItem ( 'pdfStudioSession' , JSON . stringify ( data ) ) ; } catch { /* ignore */ }
415+ return ;
416+ }
349417 return await invoke ( 'save_session' , { data : JSON . stringify ( data ) } ) ;
350418}
351419
352420export async function loadSession ( ) {
421+ if ( ! isTauri ( ) ) {
422+ try {
423+ const s = localStorage . getItem ( 'pdfStudioSession' ) ;
424+ return s ? JSON . parse ( s ) : null ;
425+ } catch { return null ; }
426+ }
353427 const result = await invoke ( 'load_session' ) ;
354428 if ( result ) {
355429 try {
0 commit comments