Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8f798c8
feat: slider component
afonsojramos Jun 28, 2024
131b622
feat: zoom slider
afonsojramos Jun 28, 2024
0f2421c
chore: add mocks
afonsojramos Jun 28, 2024
ba6ab8d
chore: add tests
afonsojramos Jun 28, 2024
55b3e1a
Merge branch 'main' into feat/zoom-slider
afonsojramos Jul 1, 2024
78c7e71
Merge branch 'main' into feat/zoom-slider
afonsojramos Jul 1, 2024
a4bf556
fix: consistent ui
afonsojramos Jul 1, 2024
7a6aa86
chore: revert electron mock
afonsojramos Jul 1, 2024
b0b36cd
fix: consistent test output
afonsojramos Jul 1, 2024
aa78899
feat: add `%` & alignment
afonsojramos Jul 1, 2024
8afd848
feat: add live zoom in settings page
afonsojramos Jul 1, 2024
bb00f1a
feat: implemented multiplier
afonsojramos Jul 1, 2024
c86e7fd
chore: set multiplier to `2` and max zoom to 120%
afonsojramos Jul 1, 2024
6f58d45
Merge branch 'main' into feat/zoom-slider
afonsojramos Jul 2, 2024
b230c08
refactor: update slider component
afonsojramos Jul 2, 2024
ef1b1ad
chore: define default step ammount
afonsojramos Jul 2, 2024
7e60474
refactor: button component
afonsojramos Jul 5, 2024
b0cf36d
chore: undo zoom specific changes
afonsojramos Jul 5, 2024
93d3a89
Merge branch 'main' into feat/zoom-slider
afonsojramos Jul 5, 2024
6664dd0
fix: button in light mode
afonsojramos Jul 5, 2024
edd6642
feat: zoom buttons
afonsojramos Jul 5, 2024
1228182
Merge branch 'main' into feat/zoom-buttons
afonsojramos Jul 7, 2024
ac28071
Merge branch 'main' into feat/zoom-buttons
afonsojramos Jul 8, 2024
4bc16b3
feat: zoom buttons
afonsojramos Jul 8, 2024
3a51451
feat: saving zoom to settings
afonsojramos Jul 8, 2024
a3ab530
Merge branch 'main' into feat/zoom-buttons
afonsojramos Jul 8, 2024
8f8d717
fix: loading zoomPercentage settings
afonsojramos Jul 8, 2024
7e3098b
Merge branches 'feat/zoom-buttons' and 'feat/zoom-buttons' of github.…
afonsojramos Jul 8, 2024
0794b3c
fix: set zoom level from defaultSettings if no Zoom Percentage set
afonsojramos Jul 9, 2024
50c60d5
chore: add default behaviour to handle edge cases
afonsojramos Jul 9, 2024
eab5a6e
fix: snapshot
afonsojramos Jul 9, 2024
45d2a3a
Merge branch 'main' into feat/zoom-buttons
afonsojramos Jul 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@
"nock": "13.5.4",
"postcss": "8.4.39",
"postcss-loader": "8.1.1",
"resize-observer-polyfill": "1.5.1",
"rimraf": "5.0.8",
"style-loader": "4.0.0",
"tailwindcss": "3.4.4",
Expand All @@ -150,6 +151,6 @@
"packageManager": "[email protected]",
"lint-staged": {
"*.{js,json,ts,tsx}": "biome format --fix",
"*.{js,ts,tsx}": "pnpm test -- --onlyChanged -u --passWithNoTests"
"*.{js,ts,tsx}": "pnpm test -- --findRelatedTests -u --passWithNoTests"
}
}
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions src/__mocks__/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ module.exports = {
shell: {
openExternal: jest.fn(),
},
webFrame: {
setZoomLevel: jest.fn(),
getZoomLevel: jest.fn(),
},
};
1 change: 1 addition & 0 deletions src/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const mockSettings: SettingsState = {
showNotificationsCountInTray: false,
openAtStartup: false,
theme: Theme.SYSTEM,
zoomPercentage: 100,
detailedNotifications: true,
markAsDoneOnOpen: false,
showAccountHostname: false,
Expand Down
2 changes: 1 addition & 1 deletion src/components/buttons/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const buttonVariants = cva(
},
size: {
default: 'min-w-20 h-10 px-4 py-1',
xs: 'h-6 rounded-md px-2',
xs: 'h-7 rounded-md px-2 py-1',
sm: 'h-9 rounded-md px-2 py-1',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
Expand Down
86 changes: 86 additions & 0 deletions src/components/settings/AppearanceSettings.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { act, fireEvent, render, screen } from '@testing-library/react';
import { webFrame } from 'electron';
import { MemoryRouter } from 'react-router-dom';
import { mockAuth, mockSettings } from '../../__mocks__/state-mocks';
import { AppContext } from '../../context/App';
import { AppearanceSettings } from './AppearanceSettings';

global.ResizeObserver = require('resize-observer-polyfill');

describe('routes/components/settings/AppearanceSettings.tsx', () => {
const updateSetting = jest.fn();
const zoomTimeout = () => new Promise((r) => setTimeout(r, 300));

afterEach(() => {
jest.clearAllMocks();
Expand Down Expand Up @@ -34,6 +38,88 @@ describe('routes/components/settings/AppearanceSettings.tsx', () => {
expect(updateSetting).toHaveBeenCalledWith('theme', 'LIGHT');
});

it('should update the zoom value when using CMD + and CMD -', async () => {
webFrame.getZoomLevel = jest.fn().mockReturnValue(-1);

await act(async () => {
render(
<AppContext.Provider
value={{
auth: mockAuth,
settings: mockSettings,
updateSetting,
}}
>
<MemoryRouter>
<AppearanceSettings />
</MemoryRouter>
</AppContext.Provider>,
);
});

fireEvent(window, new Event('resize'));
await zoomTimeout();

expect(updateSetting).toHaveBeenCalledTimes(1);
expect(updateSetting).toHaveBeenCalledWith('zoomPercentage', 50);
});

it('should update the zoom values when using the zoom buttons', async () => {
webFrame.getZoomLevel = jest.fn().mockReturnValue(0);
webFrame.setZoomLevel = jest.fn().mockImplementation((level) => {
webFrame.getZoomLevel = jest.fn().mockReturnValue(level);
fireEvent(window, new Event('resize'));
});

await act(async () => {
render(
<AppContext.Provider
value={{
auth: mockAuth,
settings: mockSettings,
updateSetting,
}}
>
<MemoryRouter>
<AppearanceSettings />
</MemoryRouter>
</AppContext.Provider>,
);
});

await act(async () => {
fireEvent.click(screen.getByLabelText('Zoom Out'));
await zoomTimeout();
});

expect(updateSetting).toHaveBeenCalledTimes(1);
expect(updateSetting).toHaveBeenCalledWith('zoomPercentage', 90);

await act(async () => {
fireEvent.click(screen.getByLabelText('Zoom Out'));
await zoomTimeout();

expect(updateSetting).toHaveBeenCalledTimes(2);
expect(updateSetting).toHaveBeenNthCalledWith(2, 'zoomPercentage', 80);
});

await act(async () => {
fireEvent.click(screen.getByLabelText('Zoom In'));
await zoomTimeout();

expect(updateSetting).toHaveBeenCalledTimes(3);
expect(updateSetting).toHaveBeenNthCalledWith(3, 'zoomPercentage', 90);
});

await act(async () => {
fireEvent.click(screen.getByLabelText('Reset Zoom'));
await zoomTimeout();

expect(updateSetting).toHaveBeenCalledTimes(4);
expect(updateSetting).toHaveBeenNthCalledWith(4, 'zoomPercentage', 100);
});
});

it('should toggle detailed notifications checkbox', async () => {
await act(async () => {
render(
Expand Down
65 changes: 63 additions & 2 deletions src/components/settings/AppearanceSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,25 @@ import {
PaintbrushIcon,
TagIcon,
} from '@primer/octicons-react';
import { ipcRenderer } from 'electron';
import { type FC, useContext, useEffect } from 'react';
import { ipcRenderer, webFrame } from 'electron';
import { type FC, useContext, useEffect, useState } from 'react';
import { AppContext } from '../../context/App';
import { Size, Theme } from '../../types';
import { setTheme } from '../../utils/theme';
import { zoomLevelToPercentage, zoomPercentageToLevel } from '../../utils/zoom';
import { Button } from '../buttons/Button';
import { Checkbox } from '../fields/Checkbox';
import { RadioGroup } from '../fields/RadioGroup';
import { Legend } from './Legend';

let timeout: NodeJS.Timeout;
const DELAY = 200;

export const AppearanceSettings: FC = () => {
const { settings, updateSetting } = useContext(AppContext);
const [zoomPercentage, setZoomPercentage] = useState(
zoomLevelToPercentage(webFrame.getZoomLevel()),
);

useEffect(() => {
ipcRenderer.on('gitify:update-theme', (_, updatedTheme: Theme) => {
Expand All @@ -27,6 +35,17 @@ export const AppearanceSettings: FC = () => {
});
}, [settings.theme]);

window.addEventListener('resize', () => {
// clear the timeout
clearTimeout(timeout);
// start timing for event "completion"
timeout = setTimeout(() => {
const zoomPercentage = zoomLevelToPercentage(webFrame.getZoomLevel());
setZoomPercentage(zoomPercentage);
updateSetting('zoomPercentage', zoomPercentage);
}, DELAY);
});

return (
<fieldset>
<Legend icon={PaintbrushIcon}>Appearance</Legend>
Expand All @@ -44,6 +63,48 @@ export const AppearanceSettings: FC = () => {
}}
className="mb-0"
/>
<div className="flex">
<label
htmlFor="Zoom"
className="mr-3 content-center font-medium text-sm text-gray-700 dark:text-gray-200"
>
Zoom:
</label>
<Button
label="Zoom Out"
onClick={() =>
zoomPercentage > 0 &&
webFrame.setZoomLevel(zoomPercentageToLevel(zoomPercentage - 10))
}
className="rounded-r-none"
size="xs"
>
-
</Button>
<span className="flex w-16 items-center justify-center rounded-none border border-gray-300 bg-transparent text-sm text-gray-700 dark:text-gray-200">
{zoomPercentage.toFixed(0)}%
</span>
<Button
label="Zoom In"
onClick={() =>
zoomPercentage < 120 &&
webFrame.setZoomLevel(zoomPercentageToLevel(zoomPercentage + 10))
}
className="rounded-none"
size="xs"
>
+
</Button>
<Button
label="Reset Zoom"
onClick={() => webFrame.setZoomLevel(0)}
variant="destructive"
className="rounded-l-none"
size="xs"
>
X
</Button>
</div>
<Checkbox
name="detailedNotifications"
label="Detailed notifications"
Expand Down
2 changes: 2 additions & 0 deletions src/components/settings/SettingsFooter.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jest.mock('react-router-dom', () => ({
useNavigate: () => mockNavigate,
}));

global.ResizeObserver = require('resize-observer-polyfill');

describe('routes/components/settings/SettingsFooter.tsx', () => {
afterEach(() => {
jest.clearAllMocks();
Expand Down
2 changes: 2 additions & 0 deletions src/context/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ describe('context/App.tsx', () => {
keyboardShortcut: true,
groupBy: 'REPOSITORY',
filterReasons: [],
zoomPercentage: 100,
} as SettingsState,
});
});
Expand Down Expand Up @@ -438,6 +439,7 @@ describe('context/App.tsx', () => {
keyboardShortcut: true,
groupBy: 'REPOSITORY',
filterReasons: [],
zoomPercentage: 100,
} as SettingsState,
});
});
Expand Down
6 changes: 6 additions & 0 deletions src/context/App.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { webFrame } from 'electron';
import {
type ReactNode,
createContext,
Expand Down Expand Up @@ -42,6 +43,7 @@ import Constants from '../utils/constants';
import { getNotificationCount } from '../utils/notifications';
import { clearState, loadState, saveState } from '../utils/storage';
import { setTheme } from '../utils/theme';
import { zoomPercentageToLevel } from '../utils/zoom';

const defaultAuth: AuthState = {
accounts: [],
Expand All @@ -62,6 +64,7 @@ export const defaultSettings: SettingsState = {
showNotificationsCountInTray: false,
openAtStartup: false,
theme: Theme.SYSTEM,
zoomPercentage: 100,
detailedNotifications: true,
markAsDoneOnOpen: false,
showAccountHostname: false,
Expand Down Expand Up @@ -244,6 +247,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
if (existing.settings) {
setKeyboardShortcut(existing.settings.keyboardShortcut);
setSettings({ ...defaultSettings, ...existing.settings });
webFrame.setZoomLevel(
zoomPercentageToLevel(existing.settings.zoomPercentage),
);
return existing.settings;
}
}, []);
Expand Down
3 changes: 0 additions & 3 deletions src/electron/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ app.whenReady().then(async () => {
mb.tray.popUpContextMenu(contextMenu, { x: bounds.x, y: bounds.y });
});

// Force the window to retrieve its previous zoom factor
mb.window.webContents.setZoomFactor(mb.window.webContents.getZoomFactor());

// Custom key events
mb.window.webContents.on('before-input-event', (event, input) => {
if (input.key === 'Escape') {
Expand Down
2 changes: 2 additions & 0 deletions src/routes/Settings.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { mockPlatform } from '../__mocks__/utils';
import { AppContext } from '../context/App';
import { SettingsRoute } from './Settings';

global.ResizeObserver = require('resize-observer-polyfill');

const mockNavigate = jest.fn();
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Expand Down
34 changes: 34 additions & 0 deletions src/routes/__snapshots__/Settings.test.tsx.snap

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading