From 76f8fb15ac52b2107f288fa54e04b22027776257 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 5 Aug 2024 13:49:13 -0400 Subject: [PATCH 01/15] feat(accounts): set primary account Signed-off-by: Adam Setch --- src/routes/Accounts.tsx | 41 +++++- .../__snapshots__/Accounts.test.tsx.snap | 129 ++++++++++++++++++ 2 files changed, 167 insertions(+), 3 deletions(-) diff --git a/src/routes/Accounts.tsx b/src/routes/Accounts.tsx index 60e77c572..774a5d39d 100644 --- a/src/routes/Accounts.tsx +++ b/src/routes/Accounts.tsx @@ -3,6 +3,8 @@ import { PersonIcon, PlusIcon, SignOutIcon, + StarFillIcon, + StarIcon, } from '@primer/octicons-react'; import { type FC, useCallback, useContext } from 'react'; @@ -12,17 +14,19 @@ import { AuthMethodIcon } from '../components/icons/AuthMethodIcon'; import { PlatformIcon } from '../components/icons/PlatformIcon'; import { AppContext } from '../context/App'; import { BUTTON_CLASS_NAME } from '../styles/gitify'; -import { type Account, Size } from '../types'; +import { type Account, IconColor, Size } from '../types'; import { getAccountUUID } from '../utils/auth/utils'; +import { cn } from '../utils/cn'; import { updateTrayIcon, updateTrayTitle } from '../utils/comms'; import { openAccountProfile, openDeveloperSettings, openHost, } from '../utils/links'; +import { saveState } from '../utils/storage'; export const AccountsRoute: FC = () => { - const { auth, logoutFromAccount } = useContext(AppContext); + const { auth, settings, logoutFromAccount } = useContext(AppContext); const navigate = useNavigate(); const logoutAccount = useCallback( @@ -35,6 +39,12 @@ export const AccountsRoute: FC = () => { [logoutFromAccount], ); + const setAsPrimaryAccount = useCallback((account: Account) => { + auth.accounts = [account, ...auth.accounts.filter((a) => a !== account)]; + saveState({ auth, settings }); + navigate('/accounts', { replace: true }); + }, []); + const loginWithPersonalAccessToken = useCallback(() => { return navigate('/login-personal-access-token', { replace: true }); }, []); @@ -48,7 +58,7 @@ export const AccountsRoute: FC = () => {
Accounts
- {auth.accounts.map((account) => ( + {auth.accounts.map((account, i) => (
{
+ +
+ +
+ +
+ + +
+ +
+ +
+ + -
- -
- -
- -
diff --git a/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap index c06b29078..eaef3dec9 100644 --- a/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap +++ b/src/components/settings/__snapshots__/SettingsFooter.test.tsx.snap @@ -17,45 +17,3 @@ exports[`routes/components/settings/SettingsFooter.tsx app version should show p v0.0.1 `; - -exports[`routes/components/settings/SettingsFooter.tsx update available visual indicator new version available 1`] = ` - - - -`; - -exports[`routes/components/settings/SettingsFooter.tsx update available visual indicator using latest version 1`] = ` - - - -`; diff --git a/src/electron/main.js b/src/electron/main.js index b8d6f853f..dd36b70c9 100644 --- a/src/electron/main.js +++ b/src/electron/main.js @@ -5,27 +5,35 @@ const { globalShortcut, Menu, dialog, + MenuItem, } = require('electron/main'); const { menubar } = require('menubar'); -const { autoUpdater } = require('electron-updater'); const { onFirstRunMaybe } = require('./first-run'); const path = require('node:path'); const log = require('electron-log'); const fs = require('node:fs'); const os = require('node:os'); +const { autoUpdater } = require('electron-updater'); +const { updateElectronApp } = require('update-electron-app'); log.initialize(); -autoUpdater.logger = log; // TODO: Remove @electron/remote use - see #650 require('@electron/remote/main').initialize(); +// Tray Icons const idleIcon = path.resolve( `${__dirname}/../../assets/images/tray-idleTemplate.png`, ); +const idleUpdateAvailableIcon = path.resolve( + `${__dirname}/../../assets/images/tray-idle-update.png`, +); const activeIcon = path.resolve( `${__dirname}/../../assets/images/tray-active.png`, ); +const activeUpdateAvailableIcon = path.resolve( + `${__dirname}/../../assets/images/tray-active-update.png`, +); const browserWindowOpts = { width: 500, @@ -40,29 +48,32 @@ const browserWindowOpts = { }, }; -let isUpdateAvailable = false; -let isUpdateDownloaded = false; - -const contextMenu = Menu.buildFromTemplate([ - { - label: 'Check for updates', - visible: !isUpdateAvailable, - click: () => { - checkForUpdates(); - }, - }, - { - label: 'An update is available', - enabled: false, - visible: isUpdateAvailable, +const checkForUpdatesMenuItem = new MenuItem({ + label: 'Check for updates', + enabled: true, + click: () => { + autoUpdater.checkForUpdatesAndNotify(); }, - { - label: 'Restart to update', - visible: isUpdateDownloaded, - click: () => { - autoUpdater.quitAndInstall(); - }, +}); + +const updateAvailableMenuItem = new MenuItem({ + label: 'An update is available', + enabled: false, + visible: false, +}); + +const updateReadyForInstallMenuItem = new MenuItem({ + label: 'Restart to update', + visible: false, + click: () => { + autoUpdater.quitAndInstall(); }, +}); + +const contextMenu = Menu.buildFromTemplate([ + checkForUpdatesMenuItem, + updateAvailableMenuItem, + updateReadyForInstallMenuItem, { type: 'separator' }, { label: 'Developer', @@ -142,27 +153,6 @@ app.whenReady().then(async () => { mb.positioner.move('trayCenter', trayBounds); mb.window.resizable = false; }); - - // Auto Updater - checkForUpdates(); - setInterval(checkForUpdates, 24 * 60 * 60 * 1000); // 24 hours - - autoUpdater.on('update-available', () => { - log.info('Auto Updater: New update available'); - isUpdateAvailable = true; - mb.window.webContents.send('gitify:auto-updater', isUpdateAvailable); - }); - - autoUpdater.on('update-not-available', () => { - log.info('Auto Updater: Already on the latest version'); - isUpdateAvailable = false; - mb.window.webContents.send('gitify:auto-updater', isUpdateAvailable); - }); - - autoUpdater.on('update-downloaded', () => { - log.info('Auto Updater: Update downloaded'); - isUpdateDownloaded = true; - }); }); nativeTheme.on('updated', () => { @@ -186,19 +176,25 @@ app.whenReady().then(async () => { ipc.on('gitify:icon-active', () => { if (!mb.tray.isDestroyed()) { - mb.tray.setImage(activeIcon); + mb.tray.setImage( + updateAvailableMenuItem.visible + ? activeUpdateAvailableIcon + : activeIcon, + ); } }); ipc.on('gitify:icon-idle', () => { if (!mb.tray.isDestroyed()) { - mb.tray.setImage(idleIcon); + mb.tray.setImage( + updateAvailableMenuItem.visible ? idleUpdateAvailableIcon : idleIcon, + ); } }); ipc.on('gitify:update-title', (_, title) => { if (!mb.tray.isDestroyed()) { - mb.tray.setTitle(`${isUpdateAvailable ? '⤓' : ''}${title}`); + mb.tray.setTitle(title); } }); @@ -223,12 +219,40 @@ app.whenReady().then(async () => { ipc.on('gitify:update-auto-launch', (_, settings) => { app.setLoginItemSettings(settings); }); -}); -function checkForUpdates() { - log.info('Auto Updater: Checking for updates...'); - autoUpdater.checkForUpdatesAndNotify(); -} + // Auto Updater + updateElectronApp({ + updateInterval: '24 hours', + logger: log, + }); + + autoUpdater.on('checking-for-update', () => { + log.info('Auto Updater: Checking for update'); + checkForUpdatesMenuItem.enabled = false; + }); + + autoUpdater.on('error', (error) => { + log.error('Auto Updater: error checking for update', error); + checkForUpdatesMenuItem.enabled = true; + }); + + autoUpdater.on('update-available', () => { + log.info('Auto Updater: New update available'); + updateAvailableMenuItem.visible = true; + mb.tray.setToolTip('Gitify\nA new update is available'); + }); + + autoUpdater.on('update-downloaded', () => { + log.info('Auto Updater: Update downloaded'); + updateReadyForInstallMenuItem.visible = true; + mb.tray.setToolTip('Gitify\nA new update is ready to install'); + }); + + autoUpdater.on('update-not-available', () => { + log.info('Auto Updater: update not available'); + checkForUpdatesMenuItem.enabled = true; + }); +}); function takeScreenshot() { const date = new Date(); diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 3d07f80ef..2ee168061 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -1,6 +1,5 @@ import { GearIcon } from '@primer/octicons-react'; -import { ipcRenderer } from 'electron'; -import { type FC, useContext, useEffect, useState } from 'react'; +import { type FC, useContext } from 'react'; import { Header } from '../components/Header'; import { AppearanceSettings } from '../components/settings/AppearanceSettings'; import { NotificationSettings } from '../components/settings/NotificationSettings'; @@ -10,13 +9,6 @@ import { AppContext } from '../context/App'; export const SettingsRoute: FC = () => { const { resetSettings } = useContext(AppContext); - const [isUpdateAvailable, setIsUpdateAvailable] = useState(false); - - useEffect(() => { - ipcRenderer.on('gitify:auto-updater', (_, isUpdateAvailable: boolean) => { - setIsUpdateAvailable(isUpdateAvailable); - }); - }, []); return (
@@ -40,7 +32,7 @@ export const SettingsRoute: FC = () => {
- +
); }; diff --git a/src/routes/__snapshots__/Settings.test.tsx.snap b/src/routes/__snapshots__/Settings.test.tsx.snap index 54e98764e..0f0b2a506 100644 --- a/src/routes/__snapshots__/Settings.test.tsx.snap +++ b/src/routes/__snapshots__/Settings.test.tsx.snap @@ -797,28 +797,6 @@ exports[`routes/Settings.tsx should render itself & its children 1`] = ` Gitify v0.0.1 - - - - -
From e197589c728ffc0b356fedb10784553af3c669e8 Mon Sep 17 00:00:00 2001 From: shiron-dev Date: Fri, 9 Aug 2024 19:42:00 +0900 Subject: [PATCH 12/15] fix: openLinks on first login screen (#1451) * fix: openLinks * fix: load default state when non existing Signed-off-by: Adam Setch --------- Signed-off-by: Adam Setch Co-authored-by: Adam Setch --- src/context/App.tsx | 2 +- src/utils/storage.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/context/App.tsx b/src/context/App.tsx index 67857be5e..cb7dd667c 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -46,7 +46,7 @@ import { clearState, loadState, saveState } from '../utils/storage'; import { setTheme } from '../utils/theme'; import { zoomPercentageToLevel } from '../utils/zoom'; -const defaultAuth: AuthState = { +export const defaultAuth: AuthState = { accounts: [], token: null, enterpriseAccounts: [], diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 74686be28..90d4d47f4 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,9 +1,13 @@ +import { defaultAuth, defaultSettings } from '../context/App'; import type { GitifyState } from '../types'; import { Constants } from './constants'; export function loadState(): GitifyState { const existing = localStorage.getItem(Constants.STORAGE_KEY); - const { auth, settings } = (existing && JSON.parse(existing)) || {}; + const { auth, settings } = (existing && JSON.parse(existing)) || { + auth: defaultAuth, + settings: defaultSettings, + }; return { auth, settings }; } From c5823c7e717849c072418b00f824300c7e113cef Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 9 Aug 2024 08:40:09 -0400 Subject: [PATCH 13/15] fix: open links default preference Signed-off-by: Adam Setch --- src/context/App.tsx | 3 ++- src/utils/comms.test.ts | 10 ++++++++++ src/utils/comms.ts | 7 ++++++- src/utils/storage.ts | 6 +----- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/context/App.tsx b/src/context/App.tsx index cb7dd667c..77f7167e9 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -164,8 +164,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { useEffect(() => { ipcRenderer.on('gitify:reset-app', () => { - setAuth(defaultAuth); clearState(); + setAuth(defaultAuth); + setSettings(defaultSettings); }); }, []); diff --git a/src/utils/comms.test.ts b/src/utils/comms.test.ts index 3c7402ba4..e78779d14 100644 --- a/src/utils/comms.test.ts +++ b/src/utils/comms.test.ts @@ -72,6 +72,16 @@ describe('utils/comms.ts', () => { }); }); + it('should use default open preference if user settings not found', () => { + jest.spyOn(storage, 'loadState').mockReturnValue({ settings: null }); + + openExternalLink('https://www.gitify.io/' as Link); + expect(shell.openExternal).toHaveBeenCalledTimes(1); + expect(shell.openExternal).toHaveBeenCalledWith('https://www.gitify.io/', { + activate: true, + }); + }); + it('should ignore opening external local links file:///', () => { openExternalLink('file:///Applications/SomeApp.app' as Link); expect(shell.openExternal).toHaveBeenCalledTimes(0); diff --git a/src/utils/comms.ts b/src/utils/comms.ts index 13cef346e..0a11fa0c3 100644 --- a/src/utils/comms.ts +++ b/src/utils/comms.ts @@ -1,4 +1,5 @@ import { ipcRenderer, shell } from 'electron'; +import { defaultSettings } from '../context/App'; import { type Link, OpenPreference } from '../types'; import Constants from './constants'; import { loadState } from './storage'; @@ -8,8 +9,12 @@ export function openExternalLink(url: Link): void { // Load the state from local storage to avoid having to pass settings as a parameter const { settings } = loadState(); + const openPreference = settings + ? settings.openLinks + : defaultSettings.openLinks; + shell.openExternal(url, { - activate: settings.openLinks === OpenPreference.FOREGROUND, + activate: openPreference === OpenPreference.FOREGROUND, }); } } diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 90d4d47f4..74686be28 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -1,13 +1,9 @@ -import { defaultAuth, defaultSettings } from '../context/App'; import type { GitifyState } from '../types'; import { Constants } from './constants'; export function loadState(): GitifyState { const existing = localStorage.getItem(Constants.STORAGE_KEY); - const { auth, settings } = (existing && JSON.parse(existing)) || { - auth: defaultAuth, - settings: defaultSettings, - }; + const { auth, settings } = (existing && JSON.parse(existing)) || {}; return { auth, settings }; } From 333e0c941ba32dad94dd4b8b3468d6305c10ab43 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 9 Aug 2024 09:23:01 -0400 Subject: [PATCH 14/15] test: rename repository's to repository Signed-off-by: Adam Setch --- src/hooks/useNotifications.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index 730364ea7..30e6f327a 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -476,7 +476,7 @@ describe('hooks/useNotifications.ts', () => { describe('markRepoNotificationsRead', () => { const repoSlug = 'gitify-app/notifications-test'; - it("should mark a repository's notifications as read with success", async () => { + it('should mark repository notifications as read with success', async () => { nock('https://api.github.com/') .put(`/repos/${repoSlug}/notifications`) .reply(200); @@ -497,7 +497,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it("should mark a repository's notifications as read with failure", async () => { + it('should mark repository notifications as read with failure', async () => { nock('https://api.github.com/') .put(`/repos/${repoSlug}/notifications`) .reply(400); @@ -520,7 +520,7 @@ describe('hooks/useNotifications.ts', () => { }); describe('markRepoNotificationsDone', () => { - it("should mark a repository's notifications as done with success", async () => { + it('should mark repository notifications as done with success', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(200); @@ -541,7 +541,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it("should mark a repository's notifications as done with failure", async () => { + it('should mark repository notifications as done with failure', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(400); From 1800c046d2082c5837d0df83fd642eca89bc672b Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 9 Aug 2024 10:48:28 -0400 Subject: [PATCH 15/15] fix: only perform reset app on reset button interaction (#1453) Signed-off-by: Adam Setch --- src/electron/main.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/electron/main.js b/src/electron/main.js index dd36b70c9..34d0b975e 100644 --- a/src/electron/main.js +++ b/src/electron/main.js @@ -268,6 +268,7 @@ function takeScreenshot() { function resetApp() { const cancelButtonId = 0; + const resetButtonId = 1; const response = dialog.showMessageBoxSync(mb.window, { type: 'warning', @@ -279,10 +280,8 @@ function resetApp() { cancelId: cancelButtonId, }); - if (response === cancelButtonId) { - return; + if (response === resetButtonId) { + mb.window.webContents.send('gitify:reset-app'); + mb.app.quit(); } - - mb.window.webContents.send('gitify:reset-app'); - mb.app.quit(); }