Files

108 lines
4.2 KiB
TypeScript

import { contextBridge, ipcRenderer } from 'electron'
import type {
ListDirResult,
OpenFileResult,
OpenFolderResult,
OpenImageResult,
SaveDialogResult
} from './ipc-types.js'
const bufferedOpenFiles: string[][] = []
let openFilesHandler: ((paths: string[]) => void) | null = null
ipcRenderer.on('myreader:open-files', (_e, paths: string[]) => {
if (openFilesHandler) {
openFilesHandler(paths)
} else {
bufferedOpenFiles.push(paths)
}
})
export interface MyReaderApi {
openFileDialog: () => Promise<OpenFileResult>
openFolderDialog: () => Promise<OpenFolderResult>
openImageDialog: () => Promise<OpenImageResult>
saveFileDialog: (defaultPath?: string) => Promise<SaveDialogResult>
readFile: (filePath: string) => Promise<string>
writeFile: (filePath: string, content: string) => Promise<void>
listDirectory: (dirPath: string) => Promise<ListDirResult>
parentDirectory: (dirPath: string) => Promise<string>
resolveRelativePath: (baseFilePath: string, href: string) => Promise<string>
openExternal: (url: string) => Promise<void>
defaultDirectory: () => Promise<string>
deleteFile: (filePath: string) => Promise<void>
renamePath: (oldPath: string, newName: string) => Promise<{ newPath: string }>
allowClose: () => void
setMenuVisible: (visible: boolean) => Promise<void>
onRequestClose: (handler: () => void) => () => void
onMenuAction: (handler: (action: string) => void) => () => void
onOpenFiles: (handler: (paths: string[]) => void) => () => void
getPendingFilesIPC: () => Promise<string[]>
}
const api: MyReaderApi = {
openFileDialog: () => ipcRenderer.invoke('myreader:open-file-dialog'),
openFolderDialog: () => ipcRenderer.invoke('myreader:open-folder-dialog'),
openImageDialog: () => ipcRenderer.invoke('myreader:open-image-dialog'),
saveFileDialog: (defaultPath?: string) => ipcRenderer.invoke('myreader:save-dialog', defaultPath),
readFile: (filePath: string) => ipcRenderer.invoke('myreader:read-file', filePath),
writeFile: (filePath: string, content: string) =>
ipcRenderer.invoke('myreader:write-file', filePath, content),
listDirectory: (dirPath: string) => ipcRenderer.invoke('myreader:list-directory', dirPath),
parentDirectory: (dirPath: string) => ipcRenderer.invoke('myreader:parent-directory', dirPath),
resolveRelativePath: (baseFilePath: string, href: string) =>
ipcRenderer.invoke('myreader:resolve-relative-path', baseFilePath, href),
openExternal: (url: string) => ipcRenderer.invoke('myreader:open-external', url),
defaultDirectory: () => ipcRenderer.invoke('myreader:default-directory'),
deleteFile: (filePath: string) => ipcRenderer.invoke('myreader:delete-file', filePath),
renamePath: (oldPath: string, newName: string) =>
ipcRenderer.invoke('myreader:rename-path', oldPath, newName),
allowClose: () => {
ipcRenderer.send('myreader:allow-close')
},
setMenuVisible: (visible: boolean) => ipcRenderer.invoke('myreader:set-menu-visible', visible),
onRequestClose: (handler: () => void) => {
const listener = (): void => {
handler()
}
ipcRenderer.on('myreader:request-close', listener)
return () => {
ipcRenderer.removeListener('myreader:request-close', listener)
}
},
onMenuAction: (handler: (action: string) => void) => {
const listener = (_e: unknown, action: string): void => {
handler(action)
}
ipcRenderer.on('myreader:menu', listener)
return () => {
ipcRenderer.removeListener('myreader:menu', listener)
}
},
onOpenFiles: (handler: (paths: string[]) => void) => {
openFilesHandler = handler
return () => {
openFilesHandler = null
}
},
getPendingFilesIPC: async (): Promise<string[]> => {
const fromMain = await ipcRenderer.invoke('myreader:get-pending-files')
const fromBuffer = bufferedOpenFiles.flat()
bufferedOpenFiles.length = 0
const seen = new Set<string>()
const merged: string[] = []
for (const p of [...fromMain, ...fromBuffer]) {
const key = p.replace(/\\/g, '/').toLowerCase()
if (seen.has(key)) continue
seen.add(key)
merged.push(p)
}
return merged
}
}
try {
contextBridge.exposeInMainWorld('myreader', api)
} catch (e) {
console.error('[MyReader] preload contextBridge failed', e)
}