Skip to content

Commit 48159db

Browse files
committed
feat: ability to set fetch type (interval or inactivity)
Signed-off-by: Adam Setch <[email protected]>
1 parent 27caa0c commit 48159db

File tree

11 files changed

+203
-34
lines changed

11 files changed

+203
-34
lines changed

src/renderer/__mocks__/state-mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type Account,
44
type AppearanceSettingsState,
55
type AuthState,
6+
FetchType,
67
type FilterSettingsState,
78
type GitifyState,
89
type GitifyUser,
@@ -87,6 +88,7 @@ const mockAppearanceSettings: AppearanceSettingsState = {
8788

8889
const mockNotificationSettings: NotificationSettingsState = {
8990
groupBy: GroupBy.REPOSITORY,
91+
fetchType: FetchType.INTERVAL,
9092
fetchAllNotifications: true,
9193
detailedNotifications: true,
9294
showPills: true,

src/renderer/components/fields/FieldLabel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export interface IFieldLabel {
77

88
export const FieldLabel: FC<IFieldLabel> = (props: IFieldLabel) => {
99
return (
10-
<label className="mr-3 font-medium cursor-pointer" htmlFor={props.name}>
10+
<label className={'mr-1 font-medium cursor-pointer'} htmlFor={props.name}>
1111
{props.label}
1212
</label>
1313
);

src/renderer/components/fields/__snapshots__/FieldLabel.test.tsx.snap

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/renderer/components/fields/__snapshots__/RadioGroup.test.tsx.snap

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/renderer/components/settings/NotificationSettings.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Stack, Text } from '@primer/react';
1414
import { APPLICATION } from '../../../shared/constants';
1515

1616
import { AppContext } from '../../context/App';
17-
import { GroupBy, Size } from '../../types';
17+
import { FetchType, GroupBy, Size } from '../../types';
1818
import { openGitHubParticipatingDocs } from '../../utils/links';
1919
import { Checkbox } from '../fields/Checkbox';
2020
import { RadioGroup } from '../fields/RadioGroup';
@@ -41,6 +41,19 @@ export const NotificationSettings: FC = () => {
4141
value={settings.groupBy}
4242
/>
4343

44+
<RadioGroup
45+
label="Fetch type:"
46+
name="fetchType"
47+
onChange={(evt) => {
48+
updateSetting('fetchType', evt.target.value as FetchType);
49+
}}
50+
options={[
51+
{ label: 'Interval', value: FetchType.INTERVAL },
52+
{ label: 'Inactivity', value: FetchType.INACTIVITY },
53+
]}
54+
value={settings.fetchType}
55+
/>
56+
4457
<Checkbox
4558
checked={settings.fetchAllNotifications}
4659
label="Fetch all notifications"

src/renderer/context/App.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
import { useTheme } from '@primer/react';
1111

1212
import { Constants } from '../constants';
13-
import { useInterval } from '../hooks/useInterval';
13+
import { useInactivityTimer } from '../hooks/timers/useInactivityTimer';
14+
import { useIntervalTimer } from '../hooks/timers/useIntervalTimer';
1415
import { useNotifications } from '../hooks/useNotifications';
1516
import type {
1617
Account,
@@ -24,6 +25,7 @@ import type {
2425
Status,
2526
Token,
2627
} from '../types';
28+
import { FetchType } from '../types';
2729
import type { Notification } from '../typesGitHub';
2830
import { headNotifications } from '../utils/api/client';
2931
import type {
@@ -142,11 +144,25 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
142144
settings.filterReasons,
143145
]);
144146

145-
useInterval(() => {
146-
fetchNotifications({ auth, settings });
147-
}, Constants.FETCH_NOTIFICATIONS_INTERVAL_MS);
147+
useIntervalTimer(
148+
() => {
149+
fetchNotifications({ auth, settings });
150+
},
151+
settings.fetchType === FetchType.INTERVAL
152+
? Constants.FETCH_NOTIFICATIONS_INTERVAL_MS
153+
: null,
154+
);
155+
156+
useInactivityTimer(
157+
() => {
158+
fetchNotifications({ auth, settings });
159+
},
160+
settings.fetchType === FetchType.INACTIVITY
161+
? Constants.FETCH_NOTIFICATIONS_INTERVAL_MS
162+
: null,
163+
);
148164

149-
useInterval(() => {
165+
useIntervalTimer(() => {
150166
for (const account of auth.accounts) {
151167
refreshAccount(account);
152168
}

src/renderer/context/defaults.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
type AppearanceSettingsState,
33
type AuthState,
4+
FetchType,
45
type FilterSettingsState,
56
GroupBy,
67
type NotificationSettingsState,
@@ -25,6 +26,7 @@ const defaultAppearanceSettings: AppearanceSettingsState = {
2526

2627
const defaultNotificationSettings: NotificationSettingsState = {
2728
groupBy: GroupBy.REPOSITORY,
29+
fetchType: FetchType.INTERVAL,
2830
fetchAllNotifications: true,
2931
detailedNotifications: true,
3032
showPills: true,
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { useCallback, useEffect, useRef } from 'react';
2+
3+
const events = ['mousedown', 'keypress', 'click'];
4+
5+
/**
6+
* Hook that triggers a callback after a specified period of user inactivity.
7+
* User activity as defined in `events` will reset the timer.
8+
*/
9+
export const useInactivityTimer = (callback: () => void, delay: number) => {
10+
const savedCallback = useRef<(() => void) | null>(null);
11+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
12+
13+
// Remember the latest callback
14+
useEffect(() => {
15+
savedCallback.current = callback;
16+
}, [callback]);
17+
18+
// Reset the inactivity timer
19+
const resetTimer = useCallback(() => {
20+
if (timeoutRef.current) {
21+
clearTimeout(timeoutRef.current);
22+
}
23+
24+
if (delay !== null && savedCallback.current) {
25+
timeoutRef.current = setTimeout(() => {
26+
// Fire callback once inactivity threshold reached
27+
savedCallback.current();
28+
29+
// Schedule next run while still inactive
30+
resetTimer();
31+
}, delay);
32+
}
33+
}, [delay]);
34+
35+
// Set up event listeners for user activity
36+
useEffect(() => {
37+
if (delay === null) {
38+
return;
39+
}
40+
41+
// Add event listeners to track activity
42+
events.forEach((event) => {
43+
document.addEventListener(event, resetTimer, { passive: true });
44+
});
45+
46+
// Start initial timer
47+
resetTimer();
48+
49+
// Cleanup function
50+
return () => {
51+
if (timeoutRef.current) {
52+
clearTimeout(timeoutRef.current);
53+
}
54+
55+
events.forEach((event) => {
56+
document.removeEventListener(event, resetTimer);
57+
});
58+
};
59+
}, [delay, resetTimer]);
60+
};

src/renderer/hooks/useInterval.ts renamed to src/renderer/hooks/timers/useIntervalTimer.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { useEffect, useRef } from 'react';
22

3-
// Thanks to https://overreacted.io/making-setinterval-declarative-with-react-hooks/
4-
export const useInterval = (callback, delay: number) => {
5-
const savedCallback = useRef(null);
3+
/**
4+
* Hook that triggers a callback after a specified period of time.
5+
*
6+
* Thanks to https://overreacted.io/making-setinterval-declarative-with-react-hooks/
7+
*/
8+
export const useIntervalTimer = (callback: () => void, delay: number) => {
9+
const savedCallback = useRef<(() => void) | null>(null);
610

711
// Remember the latest callback.
812
useEffect(() => {

0 commit comments

Comments
 (0)