diff --git a/src/main/index.ts b/src/main/index.ts index 2a8c9c1..5805265 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -34,6 +34,29 @@ const createWindow = () => { console.log('User is authenticated: ', tokenExsits) mainWindow.webContents.once('did-finish-load', () => { mainWindow.webContents.send('login-status', tokenExsits) + if (tokenExsits) { + + // Configure scheduled sync + const syncFrequency = store.get('syncFrequency') || '0' // default to manual + configureScheduledSync(syncFrequency) + + // if token exists check if the user has set to sync on startup + const triggerOnLoad = Boolean(store.get('triggerOnLoad')) + console.log('Trigger on load: ', triggerOnLoad) + if (triggerOnLoad) { + // if sync is already in progress, don't start another one + if (Boolean(store.get('isSyncing'))) { + mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Sync already in progress...'}) + console.log('Sync already in progress') + return + } + + mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Initiating sync...'}) + const readwiseSync = new ReadwiseSync(mainWindow, store) + readwiseSync.syncHighlights(undefined, true) + console.log('Syncing highlights on load') + } + } }) // Open external links in the default browser @@ -61,9 +84,19 @@ ipcMain.on('electron-store-set', async (_, key, value) => { store.set(key, value) }) -ipcMain.handle('sync-highlights', () => { +ipcMain.handle('sync-highlights', (_event, auto?: boolean) => { + + // if sync is already in progress, don't start another one + if (Boolean(store.get('isSyncing'))) { + mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Sync already in progress...'}) + console.log('Sync already in progress') + return + } + + mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Initiating sync...'}) + const readwiseSync = new ReadwiseSync(mainWindow, store) - return readwiseSync.syncHighlights() + return readwiseSync.syncHighlights(undefined, auto) }) ipcMain.handle('connect-to-readwise', async (event: Electron.Event) => { @@ -95,14 +128,24 @@ ipcMain.handle('fetch-apple-notes-accounts', async () => { return await updateAppleNotesAccounts() }) -async function configureScheduledSync() { - const minutes = parseInt(store.get('frequency')) +ipcMain.handle('update-sync-frequency', async (_event, frequency: string) => { + return await configureScheduledSync(frequency) +}) + +async function configureScheduledSync(frequency: string) { + const minutes = parseInt(frequency) let milliseconds = minutes * 60 * 1000 // convert minutes to milliseconds console.log("Settings interval to ", milliseconds) + if (!milliseconds) { + // user set frequency to "Manual" + return '0' + } setInterval(async () => { + console.log('Syncing highlights...', new Date()) const readwiseSync = new ReadwiseSync(mainWindow, store) await readwiseSync.syncHighlights(undefined, true) }, milliseconds) + return frequency } // Quit when all windows are closed, except on macOS. There, it's common diff --git a/src/main/lib/index.ts b/src/main/lib/index.ts index c895a75..ee59fe4 100644 --- a/src/main/lib/index.ts +++ b/src/main/lib/index.ts @@ -122,12 +122,14 @@ export class ReadwiseSync { if (!notesFolder) { console.log('Readwise Official plugin: no folder selected') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'No folder selected' }) await this.handleSyncError('Sync failed') return } if (!account) { console.log('Readwise Official plugin: no account selected') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'No account selected' }) await this.handleSyncError('Sync failed') return } @@ -207,6 +209,7 @@ export class ReadwiseSync { await this.acknowledgeSyncCompleted() await this.handleSyncSuccess('Synced', exportID) this.mainWindow.webContents.send('syncing-complete') + this.mainWindow.webContents.send('toast:show', { variant: 'success', message: 'Sync completed' }) console.log('Readwise Official plugin: Synced!', exportID) console.log('Readwise Official plugin: completed sync') } @@ -280,9 +283,11 @@ export class ReadwiseSync { console.log(`Exporting Readwise data (${data.booksExported} / ${data.totalBooks}) ...`) this.mainWindow.webContents.send('export-pending', false) this.mainWindow.webContents.send('export-progress', data) + this.mainWindow.webContents.send('toast:show', { variant: 'default', message: `Exporting Readwise data (${data.booksExported} / ${data.totalBooks}` }) } else { console.log('Building export...') this.mainWindow.webContents.send('export-pending', true) + this.mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Building export...' }) } // wait 1 second @@ -291,10 +296,13 @@ export class ReadwiseSync { await this.getExportStatus(statusID, token, uuid) } else if (SUCCESS_STATUSES.includes(data.taskStatus)) { this.mainWindow.webContents.send('export-complete', {}) + this.mainWindow.webContents.send('toast:show', { variant: 'success', message: 'Export completed' }) + console.log('Export completed') await this.downloadExport(statusID) } else { console.log('Readwise Official plugin: unknown status in getExportStatus: ', data) this.mainWindow.webContents.send('export-error', 'Sync failed') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'Sync failed' }) await this.handleSyncError('Sync failed') return } @@ -343,6 +351,7 @@ export class ReadwiseSync { async queueExport(statusId?: number, auto?: boolean): Promise { if (this.store.get('isSyncing')) { console.log('Readwise sync already in progress') + this.mainWindow.webContents.send('toast:show', { variant: 'default', message: 'Sync already in progress' }) return 'Sync already in progress' } @@ -356,12 +365,14 @@ export class ReadwiseSync { if (!readwiseDir) { console.log('Readwise Official plugin: no folder selected') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'No folder selected' }) await this.handleSyncError('Sync failed') return 'Sync failed' } if (!account) { console.log('Readwise Official plugin: no account selected') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'No account selected' }) await this.handleSyncError('Sync failed') return 'Sync failed' } @@ -383,6 +394,7 @@ export class ReadwiseSync { if (!folderCreated) { console.log('Readwise Official plugin: failed to create folder') await this.handleSyncError('Sync failed') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'Error: Failed to create folder' }) return 'Sync failed' } else { console.log('Readwise Official plugin: folder created') @@ -418,6 +430,7 @@ export class ReadwiseSync { } catch (e) { console.log('Readwise Official plugin: fetch failed in queueExport: ', e) await this.handleSyncError('Sync failed') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'Synced failed' }) return 'Sync failed' } @@ -428,6 +441,7 @@ export class ReadwiseSync { if (!data) { console.log('Readwise Official plugin: no data in queueExport') await this.handleSyncError('Sync failed') + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'Synced failed' }) return 'Sync failed' } @@ -436,6 +450,7 @@ export class ReadwiseSync { if (data.latest_id <= lastest_id) { await this.handleSyncSuccess() // Data is already up to date console.log('Readwise data is already up to date') + this.mainWindow.webContents.send('toast:show', { variant: 'success', message: 'Data is already up to date' }) return 'Data is already up to date' } @@ -454,11 +469,13 @@ export class ReadwiseSync { 'Latest Readwise sync already happended on your other device. Data should be up to date: ', response ) + this.mainWindow.webContents.send('toast:show', { variant: 'success', message: 'Data is already up to date' }) return 'Data is already up to date' } } else { console.log('Readwise Official plugin: bad response in queueExport: ', response) await this.handleSyncError(this.getErrorMessageFromResponse(response)) + this.mainWindow.webContents.send('toast:show', { variant: 'destructive', message: 'Synced failed. Please try again.' }) return 'Sync failed' } } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 32dbaa9..e83b640 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -9,6 +9,8 @@ declare global { readwise: { syncHighlights: () => Promise; openCustomFormatWindow: () => void; + connectToReadwise: () => Promise; + updateSyncFrequency: (frequency: string) => Promise; }; }; } diff --git a/src/preload/index.ts b/src/preload/index.ts index 6000fc8..8ba7557 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -20,6 +20,9 @@ contextBridge.exposeInMainWorld('api', { syncHighlights() { return ipcRenderer.invoke('sync-highlights') }, + updateSyncFrequency(frequency: string) { + return ipcRenderer.invoke('update-sync-frequency', frequency) + }, openCustomFormatWindow() { ipcRenderer.invoke('open-custom-format-window') } diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 3ceb754..0937a77 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -3,11 +3,8 @@ import { Toaster } from './components/ui/toaster' import { LoginCard } from './components/login' import { SettingsOptions } from './components/settings-options' import { SyncingProgress } from './components/syncing-progress' -import { useToast } from './hooks/use-toast' export default function App() { - const { toast } = useToast() - const [isLoggedIn, setIsLoggedIn] = useState(false) const [isSyncing, setIsSyncing] = useState(false) @@ -19,15 +16,13 @@ export default function App() { useEffect(() => { checkLoginStatus() - window.api.on('login-status', (event, loggedIn) => { + window.api.on('login-status', (_event, loggedIn: boolean) => { setIsLoggedIn(loggedIn) if (loggedIn) { - toast({ - variant: 'success', - description: 'Successfully connected to Readwise', - duration: 5000 - }) + console.log('Logged in') + } else { + console.log('Logged out') } }) diff --git a/src/renderer/components/settings-options.tsx b/src/renderer/components/settings-options.tsx index 62a6c37..c9d28be 100644 --- a/src/renderer/components/settings-options.tsx +++ b/src/renderer/components/settings-options.tsx @@ -89,10 +89,12 @@ export function SettingsOptions({ onIsSyncing }: SettingsOptionsProps) { }) } - const handleFrequencyChange = (e: React.ChangeEvent) => { + const handleFrequencyChange = async (e: React.ChangeEvent) => { const selectedFrequency = e.target.value setSyncFrequency(selectedFrequency) saveFrequency(selectedFrequency) + const msg = await window.api.readwise.updateSyncFrequency(selectedFrequency) + console.log('Sync frequency updated to: ', msg) toast({ variant: 'default', description: 'Sync frequency updated to "' + frequencyOptions.find((f) => f.value === selectedFrequency)?.label + '"', @@ -103,7 +105,7 @@ export function SettingsOptions({ onIsSyncing }: SettingsOptionsProps) { async function handleSyncHighlights() { onIsSyncing(true) // Start syncing try { - const msg = await window.api.readwise.syncHighlights() + const msg = await window.api.readwise.syncHighlights(undefined, false) toast({ variant: 'success', description: msg, @@ -127,6 +129,24 @@ export function SettingsOptions({ onIsSyncing }: SettingsOptionsProps) { window.api.readwise.openCustomFormatWindow() } + useEffect(() => { + async function handleSyncProgressMessages(_event, data) { + console.log('Sync progress', data.message) + // if toast already on screen, clear it and show the new one + toast({ + variant: data.variant, + description: data.message, + duration: 5000 + }) + } + + window.api.on('toast:show', handleSyncProgressMessages) + + return () => { + window.api.removeAllListeners('toast:show') + } + }, []) + return ( <>