diff --git a/forge.config.ts b/forge.config.ts index ff7c106..4be4539 100644 --- a/forge.config.ts +++ b/forge.config.ts @@ -28,7 +28,8 @@ const config: ForgeConfig = { 'resources/icon_red.png', 'resources/icon_yellow.png', 'resources/icon_blue.png', - 'resources/DOG_MEME.avif' + 'resources/DOG_MEME.avif', + 'resources/trayIcon.png' ] }, rebuildConfig: {}, diff --git a/package-lock.json b/package-lock.json index 492fa6c..dd2ce1e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1315,6 +1315,28 @@ "node": ">= 6" } }, + "node_modules/@electron/asar/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@electron/fuses": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", @@ -4100,6 +4122,27 @@ "node": ">= 8.0.0" } }, + "node_modules/@vercel/nft/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vercel/nft/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4860,6 +4903,29 @@ "node": ">= 6" } }, + "node_modules/asar/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -7010,6 +7076,29 @@ "node": ">=10" } }, + "node_modules/electron-installer-common/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/electron-installer-debian": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz", @@ -9403,27 +9492,6 @@ "giget": "dist/cli.mjs" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -14201,6 +14269,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -15467,6 +15556,29 @@ "node": ">=6.0.0" } }, + "node_modules/temp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/temp/node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", diff --git a/resources/trayIcon.png b/resources/trayIcon.png new file mode 100644 index 0000000..2d29ad7 Binary files /dev/null and b/resources/trayIcon.png differ diff --git a/src/main.ts b/src/main.ts index 0d31401..3e2536a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import { Notification, Menu, MenuItemConstructorOptions, + Tray, nativeImage } from 'electron' import { Worker } from 'worker_threads' @@ -16,7 +17,15 @@ import fs from 'fs' import schedule from 'node-schedule' import dotenv from 'dotenv' import Store from 'electron-store' -import { StoreSchema, SiteTimeTracker, DeepWorkHours, MessageType, User, AppIcon, TrackerType } from './types' +import { + StoreSchema, + SiteTimeTracker, + DeepWorkHours, + MessageType, + User, + AppIcon, + TrackerType +} from './types' import { updateSiteTimeTracker, getBrowserURL, @@ -56,6 +65,7 @@ let currentDeepWork = 0 let user: User | null = null let iconPath = '' let mainWindow: BrowserWindow | null = null +let tray: Tray | null = null log.transports.file.level = 'debug' log.transports.file.maxSize = 10 * 1024 * 1024 @@ -93,52 +103,6 @@ function setupEnvironment() { } } -export function updateAppMenu() { - createAppMenu() -} - -function createAppMenu() { - const totalDeepWorkHours = getDeepWorkHours() - - const menuTemplate: MenuItemConstructorOptions[] = [ - { - label: 'DeepFocus', - submenu: [ - { - label: `Total Deep Work: ${totalDeepWorkHours} hours`, - enabled: false - }, - { type: 'separator' }, - { - label: 'Reset Data', - click: () => { - handleDailyReset() - new Notification({ - title: 'DeepFocus', - body: 'Daily data has been reset.', - icon: join(resourcesPath, 'icon.png') - }).show() - updateAppMenu() - } - }, - { type: 'separator' }, - { - label: 'Quit', - click: () => { - app.quit() - } - } - ] - } - ] - - // Create the menu from the template - const menu = Menu.buildFromTemplate(menuTemplate) - - // Set the application menu - Menu.setApplicationMenu(menu) -} - // Store user data in the electron-store and send to worker export function handleUserData(user: User, store: TypedStore): User { store.set('user', { @@ -161,11 +125,14 @@ export function loadUserData() { const savedUser: User | null = store.get('user') || null if (savedUser) { schedulerWorker.postMessage({ type: MessageType.SET_USER_INFO, user: savedUser }) + const iconPath = app.isPackaged + ? path.join(process.resourcesPath, 'icon.png') + : path.join(__dirname, '../../resources/icon.png') new Notification({ title: 'DeepFocus', body: 'Welcome back, ' + savedUser.firstName, - icon: join(resourcesPath, 'icon.png') + icon: iconPath }).show() } return savedUser @@ -266,11 +233,9 @@ export function startActivityMonitoring() { return } - // console.log(`Active Application: ${appName}`) let URL = '' if (isBrowser(appName)) { - URL = getBaseURL(await getBrowserURL(appName)) } @@ -311,7 +276,8 @@ async function createWindow(): Promise { preload: path.join(__dirname, 'preload.js'), nodeIntegrationInWorker: true, sandbox: false - } + }, + icon: iconPath }) app.dock.setIcon(getIconPath('icon.png', resourcesPath)) @@ -324,7 +290,6 @@ async function createWindow(): Promise { return { action: 'deny' } }) - // and load the index.html of the app. if (MAIN_WINDOW_VITE_DEV_SERVER_URL) { mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL) @@ -347,6 +312,9 @@ async function createWindow(): Promise { } app.whenReady().then(async () => { + const iconPath = app.isPackaged + ? path.join(process.resourcesPath, 'trayIcon.png') + : path.join(__dirname, '../../resources/trayIcon.png') log.info('app is ready. Retrieving currentSiteTimeTrackers and deepWorkHours from store') currentSiteTimeTrackers = store.get('siteTimeTrackers', []) deepWorkHours = store.get('deepWorkHours', { @@ -365,16 +333,61 @@ app.whenReady().then(async () => { setupIPCListeners() user = loadUserData() setupPeriodicSave() + console.log('updating app menu') } catch (error) { console.error('Error during permission check or timeout:', error) new Notification({ title: 'DeepFocus', body: `Deep Focus can't function properly without permissions.`, - icon: join(__dirname, 'resources/icon.png') + icon: iconPath }) } }) + const image = nativeImage.createFromPath(iconPath) + tray = new Tray(image) + tray.setToolTip('Deep Focus. Get more done.') + + const trayMenu = Menu.buildFromTemplate([ + { + label: 'Total Deep Work', + click: () => { + const today = dayjs().format('dddd') as keyof typeof deepWorkHours + const totalDeepWorkHours = getDeepWorkHours()[today] + new Notification({ + title: 'DeepFocus', + body: `Total Deep Work: ${totalDeepWorkHours} hours`, + icon: iconPath + }).show() + } + }, + { + label: 'Reset Data', + click: () => { + handleDailyReset() // Replace with your function to reset data + new Notification({ + title: 'DeepFocus', + body: 'Daily data has been reset.', + icon: iconPath + }).show() + } + }, + { type: 'separator' }, + { + label: 'Quit', + click: () => { + app.quit() + } + } + ]) + + tray.setContextMenu(trayMenu) + + tray.on('click', () => { + if (mainWindow) { + mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show() + } + }) }) app.on('ready', () => { @@ -408,10 +421,13 @@ export function handleUserLogout(): void { store.set('lastResetDate', dayjs().toISOString()) user = null stopActivityMonitoring() + const iconPath = app.isPackaged + ? path.join(process.resourcesPath, 'icon.png') + : path.join(__dirname, '../../resources/icon.png') new Notification({ title: 'DeepFocus', body: 'You have been logged out', - icon: join(__dirname, 'resources/icon.png') + icon: iconPath }).show() } @@ -518,19 +534,19 @@ function setupIPCListeners() { ipcMain.handle('get-icon', async (_event, iconPath) => { try { - const image = nativeImage.createFromPath(iconPath); - + const image = nativeImage.createFromPath(iconPath) + if (image.isEmpty()) { - console.warn(`Icon at path "${iconPath}" could not be loaded.`); - return null; // Indicate that the icon could not be loaded + console.warn(`Icon at path "${iconPath}" could not be loaded.`) + return null // Indicate that the icon could not be loaded } - - return image.toDataURL(); + + return image.toDataURL() } catch (error) { - console.error(`Failed to load icon from path "${iconPath}":`, error); - return null; // Handle error by returning a default fallback + console.error(`Failed to load icon from path "${iconPath}":`, error) + return null // Handle error by returning a default fallback } - }); + }) // Fetch the user's site time trackers ipcMain.on('fetch-site-trackers', async (event) => { @@ -574,7 +590,7 @@ function setupIPCListeners() { // Update the user's deep work target daily ipcMain.on('update-deep-work-target', (_event, newTarget: number) => { store.set('deepWorkTarget', newTarget) - updateAppMenu() + // updateAppMenu() console.log(`Updated Deep Work Target: ${newTarget}`) }) // Fetch the user's current deep work hours daily diff --git a/src/productivityUtils.ts b/src/productivityUtils.ts index f754dc8..f9d9a1d 100755 --- a/src/productivityUtils.ts +++ b/src/productivityUtils.ts @@ -190,7 +190,7 @@ export function getActiveWindowApp(): Promise { }) } // Function to get the URL for a specific browser -export function getBrowserURL(browser: browser): Promise { +export function getBrowserURL(browser: string): Promise { return new Promise((resolve, _reject) => { let script = `osascript -e 'tell application "${browser}" to get URL of active tab of front window'` if (browser === 'Safari') { diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 7dee9bb..700c2bf 100755 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -103,11 +103,15 @@ const App = (props: ComponentProps) => { tour.start() } - + // Initialize the tour for new users createEffect(() => { console.log('checking if logged in and new user') - if((localStorage.getItem('onboarded') === 'false' || !localStorage.getItem('onboarded')) && localStorage.getItem('token') && localStorage.getItem('user')) { + if ( + (localStorage.getItem('onboarded') === 'false' || !localStorage.getItem('onboarded')) && + localStorage.getItem('token') && + localStorage.getItem('user') + ) { initializeTour() localStorage.setItem('onboarded', 'true') setIsNewUser(false) @@ -133,8 +137,9 @@ const App = (props: ComponentProps) => { setIsLoggedIn(true) sendUserToBackend(JSON.parse(user)) setIsNewUser(false) - navigate('/onboarding') } + // TODO: better navigate user to home page to show Home component + navigate('/') }) const NavBar = () => { diff --git a/src/utils.ts b/src/utils.ts index 58f7df2..dd2a864 100755 --- a/src/utils.ts +++ b/src/utils.ts @@ -100,7 +100,7 @@ export function updateIconBasedOnProgress( const isIconPathChanged = iconPath !== newIconPath if (isIconPathChanged) { log.info('Comparison of icon paths is ', iconPath, newIconPath) - app.dock.setIcon(iconPath) + // app.dock.setIcon(iconPath) new Notification({ title: 'DeepFocus', body: message, diff --git a/src/worker.ts b/src/worker.ts index e2f358e..11ffc24 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -33,7 +33,7 @@ parentPort?.on('message', (message) => { } }) -function requestData() { +function requestData(): void { parentPort?.postMessage({ type: MessageType.GET_DATA }) }