From c8b3c35d531a2cd848234f5679344f369033780e Mon Sep 17 00:00:00 2001 From: Aaron Lichtman Date: Fri, 17 Jan 2025 13:48:20 -0800 Subject: [PATCH] WIP: right click -> open with macos file support --- docs/release.md | 4 ++- package.json | 10 ++++++++ src/main/main.ts | 8 ++++++ src/main/preload.ts | 3 +++ src/renderer/App.tsx | 35 ++++++++++++++++----------- src/renderer/EncryptOrDecryptForm.tsx | 14 +++++------ src/renderer/FileUpload.tsx | 8 +++--- src/renderer/preload.d.ts | 1 + 8 files changed, 57 insertions(+), 26 deletions(-) diff --git a/docs/release.md b/docs/release.md index c38db7a..01aa774 100644 --- a/docs/release.md +++ b/docs/release.md @@ -22,8 +22,10 @@ $ npm run package ## TL;DR -``` +```bash +# on macOS $ npm run release +# a draft github release will be created ``` // TODO: Then, some more work needs to be done -- upload to Flathub, snap, dnf copr, aur, and homebrew. Update the URLs in the [Homebrew taps repo](https://www.github.com/alichtman/homebrew-taps). diff --git a/package.json b/package.json index d9b4e43..b2cc7ff 100644 --- a/package.json +++ b/package.json @@ -210,6 +210,16 @@ "arm64" ] }, + "fileAssociations": [ + { + "ext": [ + "dbolt", + "deadbolt" + ], + "description": "Encrypted files", + "role": "Viewer" + } + ], "type": "distribution", "hardenedRuntime": true, "entitlements": "assets/entitlements.mac.plist", diff --git a/src/main/main.ts b/src/main/main.ts index 76bd0e1..2a07aeb 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -156,6 +156,14 @@ const createWindow = async () => { * Add event listeners... */ +app.on('open-file', (event, fileOpenedWithDeadbolt) => { + event.preventDefault(); + console.log('open-file', fileOpenedWithDeadbolt); + if (mainWindow) { + mainWindow.webContents.send('file-opened', fileOpenedWithDeadbolt); + } +}); + app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed diff --git a/src/main/preload.ts b/src/main/preload.ts index ae0c205..5687f88 100644 --- a/src/main/preload.ts +++ b/src/main/preload.ts @@ -11,4 +11,7 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.invoke('prettyPrintFilePath', [filePath]), revealFileInFinder: (filePath: string) => ipcRenderer.invoke('revealFileInFinder', [filePath]), + handleFileOpen: (callback: (filePath: string) => void) => { + ipcRenderer.on('file-opened', (_event, filePath) => callback(filePath)); + }, }); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ed7ba26..4652b39 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-alert */ /* eslint-disable no-else-return */ /* eslint-disable no-console */ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import './App.css'; import CircularProgress from '@mui/material/CircularProgress'; import FileUpload from './FileUpload'; @@ -45,10 +45,10 @@ export function isDeadboltFile(filePath: string | undefined): boolean { } export default function App() { - // This is just a handle on the file for the path -- you can't actually read or write to it in the renderer process. - const [fileToWorkWith, setFileToWorkWith] = useState( - undefined, - ); + // This is just a handle on the file path + const [filePathToWorkWith, setFilePathToWorkWith] = useState< + string | undefined + >(undefined); const [pathToEncryptedOrDecryptedFile, setPathToEncryptedOrDecryptedFile] = useState(undefined); const [viewState, setViewState] = useState(ViewState.FILE_UPLOAD); @@ -61,7 +61,7 @@ export default function App() { const resetToFileUpload = () => { setViewState(ViewState.FILE_UPLOAD); - setFileToWorkWith(undefined); + setFilePathToWorkWith(undefined); }; const revealInFinder = () => { @@ -129,15 +129,22 @@ export default function App() { }); }; - const handleFileSelection = (file: File) => { - const isEncrypted = isDeadboltFile(file.name); + const handleFileSelection = (filePath: string) => { + const isEncrypted = isDeadboltFile(filePath); console.log('File is encrypted?', isEncrypted); - console.log('File path:', file.name); - setFileToWorkWith(file); + console.log('File path:', filePath); + setFilePathToWorkWith(filePath); setFileIsEncrypted(isEncrypted); setViewState(ViewState.ENCRYPT_OR_DECRYPT); }; + useEffect(() => { + window.electronAPI.handleFileOpen((filePath) => { + console.log('handleFileOpen', filePath); + handleFileSelection(filePath); + }); + }, []); + if (loading) { return (
; + if (viewState === ViewState.FILE_UPLOAD || !filePathToWorkWith) { + return ; } else if (viewState === ViewState.ENCRYPT_OR_DECRYPT && !fileIsEncrypted) { return ( { resetToFileUpload(); @@ -170,7 +177,7 @@ export default function App() { } else if (viewState === ViewState.ENCRYPT_OR_DECRYPT && fileIsEncrypted) { return ( { resetToFileUpload(); diff --git a/src/renderer/EncryptOrDecryptForm.tsx b/src/renderer/EncryptOrDecryptForm.tsx index 507bafb..ff11675 100644 --- a/src/renderer/EncryptOrDecryptForm.tsx +++ b/src/renderer/EncryptOrDecryptForm.tsx @@ -9,12 +9,12 @@ import EncryptOrDecryptFileHeader from './EncryptOrDecryptFileHeader'; export default function EncryptOrDecryptForm({ isDecryption, - file, + filePath, onSubmit, onCancel, }: { isDecryption: boolean; - file: File; + filePath: string; onSubmit: (filePath: string, password: string) => void; onCancel: () => void; }): React.ReactNode | null { @@ -44,9 +44,9 @@ export default function EncryptOrDecryptForm({ const onKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { if (isDecryption) { - onSubmit(file.path, password); + onSubmit(filePath, password); } else if (validatePassword()) { - onSubmit(file.path, password); + onSubmit(filePath, password); } else { // Encryption, passwords don't match setDisplayError(true); @@ -59,7 +59,7 @@ export default function EncryptOrDecryptForm({ return ( <> - +
{ if (!isDecryption && validatePassword()) { - onSubmit(file.path, password); + onSubmit(filePath, password); } else if (isDecryption) { - onSubmit(file.path, password); + onSubmit(filePath, password); } }} > diff --git a/src/renderer/FileUpload.tsx b/src/renderer/FileUpload.tsx index c6f0529..04371d2 100644 --- a/src/renderer/FileUpload.tsx +++ b/src/renderer/FileUpload.tsx @@ -28,14 +28,14 @@ function DeadboltVersionTagAndGithubLink() { } export default function FileUpload({ - setFileToWorkWith, + setFilePathToWorkWith, }: { - setFileToWorkWith: (file: File) => void; + setFilePathToWorkWith: (filepath: string) => void; }) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const onSelectFromFileBrowser = (files: File[], _event: DropEvent) => { console.log('File dropped:', files); - setFileToWorkWith(files[0]); + setFilePathToWorkWith(files[0].path); }; // Drag-and-drop doesn't give us the file path, so we need to use this super hacky workaround: https://github.com/react-dropzone/file-selector/issues/10#issuecomment-2482649010 @@ -44,7 +44,7 @@ export default function FileUpload({ event.preventDefault(); const file = event.dataTransfer?.files[0]; if (file != null) { - setFileToWorkWith(file); + setFilePathToWorkWith(file.path); } }; diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index f8adc70..089fc0c 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -11,6 +11,7 @@ declare global { password: string, ) => Promise; prettyPrintFilePath: (filePath: string) => Promise; + handleFileOpen: (callback: (filePath: string) => void) => void; }; } }