From 326946fefc1e16052b0c8c25157d1a147729bf0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Jim=C3=A9nez?= Date: Tue, 23 Jan 2024 01:11:37 +0100 Subject: [PATCH] refactor(store): move old redux code to redux toolkit (#594) --- app/renderer/package.json | 2 +- app/renderer/src/App.tsx | 7 +- app/renderer/src/components/Layout.tsx | 22 +- app/renderer/src/components/Navigation.tsx | 12 +- app/renderer/src/components/Titlebar.tsx | 10 +- app/renderer/src/components/TraySVG.tsx | 11 +- app/renderer/src/components/Updater.tsx | 20 +- app/renderer/src/config.ts | 2 +- app/renderer/src/contexts/CounterContext.tsx | 204 +++++++++--------- app/renderer/src/contexts/ThemeContext.tsx | 10 +- .../contexts/connectors/ElectronConnector.tsx | 9 +- .../contexts/connectors/TauriConnector.tsx | 11 +- app/renderer/src/hooks/storeHooks.ts | 10 + app/renderer/src/hooks/useTrayIconUpdates.tsx | 5 +- .../src/routes/Config/ConfigHeader.tsx | 4 +- .../src/routes/Config/SliderSection.tsx | 9 +- .../src/routes/Config/SpecialBreaks.tsx | 7 +- .../src/routes/Config/SpecialField.tsx | 11 +- .../src/routes/Settings/FeatureSection.tsx | 31 +-- .../src/routes/Settings/SettingHeader.tsx | 4 +- app/renderer/src/routes/Settings/index.tsx | 8 +- .../Timer/Control/CompactModeButton.tsx | 7 +- .../src/routes/Timer/Control/Control.tsx | 37 ++-- .../src/routes/Timer/Control/Sessions.tsx | 4 +- .../src/routes/Timer/Counter/Counter.tsx | 7 +- .../src/routes/Timer/Counter/CounterLabel.tsx | 10 +- .../src/routes/Timer/Counter/CounterTimer.tsx | 4 +- .../src/routes/Timer/Counter/CounterType.tsx | 14 +- app/renderer/src/routes/Timer/index.tsx | 7 +- app/renderer/src/store/config/actions.ts | 90 -------- .../src/store/config/defaultConfig.ts | 30 +++ app/renderer/src/store/config/index.ts | 81 ++++++- app/renderer/src/store/config/reducer.ts | 113 ---------- app/renderer/src/store/config/types.ts | 65 +----- app/renderer/src/store/settings/actions.ts | 156 -------------- .../src/store/settings/defaultSettings.ts | 20 ++ app/renderer/src/store/settings/index.ts | 136 +++++++++++- app/renderer/src/store/settings/reducer.ts | 132 ------------ app/renderer/src/store/settings/types.ts | 42 +--- app/renderer/src/store/store.ts | 32 +-- app/renderer/src/store/timer/actions.ts | 44 ---- app/renderer/src/store/timer/defaultTimer.ts | 7 + app/renderer/src/store/timer/index.ts | 42 +++- app/renderer/src/store/timer/reducer.ts | 44 ---- app/renderer/src/store/timer/types.ts | 57 +---- app/renderer/src/store/update/actions.ts | 24 --- app/renderer/src/store/update/index.ts | 43 +++- app/renderer/src/store/update/reducer.ts | 38 ---- app/renderer/src/store/update/types.ts | 13 -- .../src/styles/components/navigation.ts | 10 +- .../src/styles/routes/timer/control.ts | 35 +-- .../src/styles/routes/timer/counter.ts | 17 +- yarn.lock | 33 ++- 53 files changed, 677 insertions(+), 1126 deletions(-) create mode 100644 app/renderer/src/hooks/storeHooks.ts delete mode 100644 app/renderer/src/store/config/actions.ts create mode 100644 app/renderer/src/store/config/defaultConfig.ts delete mode 100644 app/renderer/src/store/config/reducer.ts delete mode 100644 app/renderer/src/store/settings/actions.ts create mode 100644 app/renderer/src/store/settings/defaultSettings.ts delete mode 100644 app/renderer/src/store/settings/reducer.ts delete mode 100644 app/renderer/src/store/timer/actions.ts create mode 100644 app/renderer/src/store/timer/defaultTimer.ts delete mode 100644 app/renderer/src/store/timer/reducer.ts delete mode 100644 app/renderer/src/store/update/actions.ts delete mode 100644 app/renderer/src/store/update/reducer.ts delete mode 100644 app/renderer/src/store/update/types.ts diff --git a/app/renderer/package.json b/app/renderer/package.json index 18558f99..327645f2 100644 --- a/app/renderer/package.json +++ b/app/renderer/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@pomatez/shareables": "*", + "@reduxjs/toolkit": "2.0.1", "@tauri-apps/api": "2.0.0-alpha.11", "@tauri-apps/plugin-autostart": "2.0.0-alpha.2", "@tauri-apps/plugin-global-shortcut": "2.0.0-alpha.2", @@ -59,7 +60,6 @@ "react-redux": "^7.2.9", "react-router-dom": "^5.3.4", "redux": "^4.0.5", - "redux-devtools-extension": "^2.13.8", "redux-undo": "^1.0.1", "styled-components": "^5.3.11", "use-stay-awake": "^0.1.5" diff --git a/app/renderer/src/App.tsx b/app/renderer/src/App.tsx index c3608513..90826cbe 100644 --- a/app/renderer/src/App.tsx +++ b/app/renderer/src/App.tsx @@ -7,13 +7,10 @@ import { } from "contexts"; import { Layout, Preloader } from "components"; import { compactRoutes, routes } from "config"; -import { useSelector } from "react-redux"; -import { AppStateTypes } from "store"; +import { useAppSelector } from "hooks/storeHooks"; export default function App() { - const settings = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); useEffect(() => { const contextEvent = (event: MouseEvent) => { diff --git a/app/renderer/src/components/Layout.tsx b/app/renderer/src/components/Layout.tsx index 32b78fc3..960a6381 100644 --- a/app/renderer/src/components/Layout.tsx +++ b/app/renderer/src/components/Layout.tsx @@ -6,28 +6,20 @@ import React, { useRef, } from "react"; import { RouteComponentProps, withRouter } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { - AppStateTypes, - SHORT_BREAK, - LONG_BREAK, - SPECIAL_BREAK, - SettingTypes, -} from "store"; import { StyledLayout } from "styles"; import Titlebar from "./Titlebar"; import Navigation from "./Navigation"; import { ThemeContext } from "contexts"; +import { TimerStatus } from "store/timer/types"; +import { useAppSelector } from "hooks/storeHooks"; type Props = {} & RouteComponentProps; const Layout: React.FC = ({ history, location, children }) => { - const timer = useSelector((state: AppStateTypes) => state.timer); + const timer = useAppSelector((state) => state.timer); - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); const { toggleThemeAction } = useContext(ThemeContext); @@ -52,9 +44,9 @@ const Layout: React.FC = ({ history, location, children }) => { useEffect(() => { if (settings.enableFullscreenBreak) { if ( - timer.timerType === SHORT_BREAK || - timer.timerType === LONG_BREAK || - timer.timerType === SPECIAL_BREAK + timer.timerType === TimerStatus.SHORT_BREAK || + timer.timerType === TimerStatus.LONG_BREAK || + timer.timerType === TimerStatus.SPECIAL_BREAK ) { if (location.pathname !== "/") { setNoTransition(true); diff --git a/app/renderer/src/components/Navigation.tsx b/app/renderer/src/components/Navigation.tsx index 16da18b4..cca9bdb7 100644 --- a/app/renderer/src/components/Navigation.tsx +++ b/app/renderer/src/components/Navigation.tsx @@ -1,6 +1,5 @@ import React from "react"; -import { useSelector } from "react-redux"; -import { AppStateTypes, TimerTypes } from "store"; +import { useAppSelector } from "hooks/storeHooks"; import { StyledNav, StyledNavList, @@ -11,17 +10,16 @@ import { import { NavNotify } from "components"; import { routes } from "config"; import SVG from "./SVG"; +import { TimerStatus } from "store/timer/types"; type Props = { - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; }; const Navigation: React.FC = ({ timerType }) => { - const settings = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); - const state = useSelector((state: AppStateTypes) => state); + const state = useAppSelector((state) => state); return ( diff --git a/app/renderer/src/components/Titlebar.tsx b/app/renderer/src/components/Titlebar.tsx index ef4024a3..23e32173 100644 --- a/app/renderer/src/components/Titlebar.tsx +++ b/app/renderer/src/components/Titlebar.tsx @@ -1,5 +1,5 @@ import React, { useContext, useCallback } from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; import { StyledTitlebar, StyledWindowActions, @@ -23,7 +23,7 @@ import appIconLongBreakDark from "assets/logos/tray-dark-lb.png"; type Props = { darkMode: boolean; - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; }; const Titlebar: React.FC = ({ darkMode, timerType }) => { @@ -32,11 +32,11 @@ const Titlebar: React.FC = ({ darkMode, timerType }) => { const getAppIcon = useCallback(() => { switch (timerType) { - case "STAY_FOCUS": + case TimerStatus.STAY_FOCUS: return darkMode ? appIconDark : appIcon; - case "SHORT_BREAK": + case TimerStatus.SHORT_BREAK: return darkMode ? appIconShortBreakDark : appIconShortBreak; - case "LONG_BREAK": + case TimerStatus.LONG_BREAK: return darkMode ? appIconLongBreakDark : appIconLongBreak; default: return darkMode ? appIconLongBreakDark : appIconLongBreak; diff --git a/app/renderer/src/components/TraySVG.tsx b/app/renderer/src/components/TraySVG.tsx index 388377bd..2efdeca7 100644 --- a/app/renderer/src/components/TraySVG.tsx +++ b/app/renderer/src/components/TraySVG.tsx @@ -1,17 +1,17 @@ import React from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; type Props = { dashOffset?: number; - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; }; export const TraySVG: React.FC = ({ timerType, dashOffset }) => { const getProgressColor = (opacity = 1) => { switch (timerType) { - case "STAY_FOCUS": + case TimerStatus.STAY_FOCUS: return `rgba(0, 152, 247, ${opacity})`; - case "SHORT_BREAK": + case TimerStatus.SHORT_BREAK: return `rgba(7, 181, 131, ${opacity})`; default: return `rgba(212, 141, 10, ${opacity})`; @@ -71,7 +71,8 @@ export const TraySVG: React.FC = ({ timerType, dashOffset }) => { ); }; +//TODO: Remove this TraySVG.defaultProps = { dashOffset: 0, - timerType: "STAY_FOCUS", + timerType: TimerStatus.STAY_FOCUS, }; diff --git a/app/renderer/src/components/Updater.tsx b/app/renderer/src/components/Updater.tsx index 8a162db6..ccf47e89 100644 --- a/app/renderer/src/components/Updater.tsx +++ b/app/renderer/src/components/Updater.tsx @@ -2,13 +2,9 @@ import React from "react"; import Header from "./Header"; import styled from "styled-components/macro"; import ReactMarkdown from "react-markdown"; -import { useDispatch, useSelector } from "react-redux"; -import { AppStateTypes, setIgnoreUpdate, SettingTypes } from "../store"; -import { - setUpdateBody, - setUpdateVersion, - UpdateTypes, -} from "../store/update"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; +import { setIgnoreUpdate } from "../store"; +import { setUpdateBody, setUpdateVersion } from "../store/update"; import { StyledButtonNormal, StyledButtonPrimary, @@ -51,14 +47,10 @@ const IgnoreVersion = styled.div` `; const Updater: React.FC = () => { - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); - const update: UpdateTypes = useSelector( - (state: AppStateTypes) => state.update - ); + const settings = useAppSelector((state) => state.settings); + const update = useAppSelector((state) => state.update); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); return ( diff --git a/app/renderer/src/config.ts b/app/renderer/src/config.ts index 19958fe2..14962304 100644 --- a/app/renderer/src/config.ts +++ b/app/renderer/src/config.ts @@ -1,7 +1,7 @@ import { SVGTypes } from "components"; import { TaskList, Config, Timer, Settings } from "routes"; import { ConfigSliderProps } from "routes"; -import { AppStateTypes, SettingTypes } from "./store"; +import { AppStateTypes } from "./store"; import { DefaultRootState } from "react-redux"; export const APP_NAME = "Pomatez"; diff --git a/app/renderer/src/contexts/CounterContext.tsx b/app/renderer/src/contexts/CounterContext.tsx index c8b0652b..9e9f9c96 100644 --- a/app/renderer/src/contexts/CounterContext.tsx +++ b/app/renderer/src/contexts/CounterContext.tsx @@ -1,18 +1,6 @@ import React, { useState, useEffect, useCallback } from "react"; -import { useSelector, useDispatch } from "react-redux"; import useStayAwake from "use-stay-awake"; -import { - AppStateTypes, - STAY_FOCUS, - SHORT_BREAK, - setRound, - setTimerType, - LONG_BREAK, - TimerTypes, - SPECIAL_BREAK, - SettingTypes, - setPlay, -} from "store"; +import { setRound, setTimerType, setPlay } from "store"; import { useNotification } from "hooks"; import { padNum, isEqualToOne } from "utils"; @@ -24,11 +12,13 @@ import sessionCompletedWav from "assets/audios/session-completed.wav"; import sixtySecondsLeftWav from "assets/audios/sixty-seconds-left.wav"; import specialBreakStartedWav from "assets/audios/special-break-started.wav"; import thirtySecondsLeftWav from "assets/audios/thirty-seconds-left.wav"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; +import { TimerStatus } from "store/timer/types"; type CounterProps = { count: number; duration: number; - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; resetTimerAction?: () => void; shouldFullscreen?: boolean; }; @@ -39,16 +29,14 @@ const CounterContext = React.createContext({ }); const CounterProvider: React.FC = ({ children }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); - const { timer, config } = useSelector((state: AppStateTypes) => ({ + const { timer, config } = useAppSelector((state) => ({ timer: state.timer, config: state.config, })); - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); const { preventSleeping, allowSleeping } = useStayAwake(); @@ -73,16 +61,16 @@ const CounterProvider: React.FC = ({ children }) => { const resetTimerAction = useCallback(() => { switch (timer.timerType) { - case STAY_FOCUS: + case TimerStatus.STAY_FOCUS: setTimerDuration(config.stayFocus); break; - case SHORT_BREAK: + case TimerStatus.SHORT_BREAK: setTimerDuration(config.shortBreak); break; - case LONG_BREAK: + case TimerStatus.LONG_BREAK: setTimerDuration(config.longBreak); break; - case SPECIAL_BREAK: + case TimerStatus.SPECIAL_BREAK: setDuration(duration); setCount(duration); break; @@ -98,7 +86,7 @@ const CounterProvider: React.FC = ({ children }) => { ]); useEffect(() => { - if (timer.playing && timer.timerType !== STAY_FOCUS) { + if (timer.playing && timer.timerType !== TimerStatus.STAY_FOCUS) { preventSleeping(); } else { allowSleeping(); @@ -117,70 +105,73 @@ const CounterProvider: React.FC = ({ children }) => { const currentTime = padNum(date.getHours()) + ":" + padNum(date.getMinutes()); - if (timer.timerType !== SPECIAL_BREAK) { - switch (currentTime) { - case firstBreak.fromTime: - dispatch(setTimerType("SPECIAL_BREAK")); - setTimerDuration(firstBreak.duration); - notification( - "Special break started.", - { - body: `Enjoy your ${firstBreak.duration} ${ - isEqualToOne(firstBreak.duration) - ? "minute" - : "minutes" - } special break.`, - }, - specialBreakStartedWav - ); - break; - case secondBreak.fromTime: - dispatch(setTimerType("SPECIAL_BREAK")); - setTimerDuration(secondBreak.duration); - notification( - "Special break started.", - { - body: `Enjoy your ${secondBreak.duration} ${ - isEqualToOne(secondBreak.duration) - ? "minute" - : "minutes" - } special break.`, - }, - specialBreakStartedWav - ); - break; - case thirdBreak.fromTime: - dispatch(setTimerType("SPECIAL_BREAK")); - setTimerDuration(thirdBreak.duration); - notification( - "Special break started.", - { - body: `Enjoy your ${thirdBreak.duration} ${ - isEqualToOne(thirdBreak.duration) - ? "minute" - : "minutes" - } special break.`, - }, - specialBreakStartedWav - ); - break; - case fourthBreak.fromTime: - dispatch(setTimerType("SPECIAL_BREAK")); - setTimerDuration(fourthBreak.duration); - notification( - "Special break started.", - { - body: `Enjoy your ${fourthBreak.duration} ${ - isEqualToOne(fourthBreak.duration) - ? "minute" - : "minutes" - } special break.`, - }, - specialBreakStartedWav - ); - break; - default: - return; + if (timer.timerType !== TimerStatus.SPECIAL_BREAK) { + if (firstBreak && currentTime === firstBreak.fromTime) { + dispatch(setTimerType(TimerStatus.SPECIAL_BREAK)); + setTimerDuration(firstBreak.duration); + notification( + "Special break started.", + { + body: `Enjoy your ${firstBreak.duration} ${ + isEqualToOne(firstBreak.duration) + ? "minute" + : "minutes" + } special break.`, + }, + specialBreakStartedWav + ); + return; + } + + if (secondBreak && currentTime === secondBreak.fromTime) { + dispatch(setTimerType(TimerStatus.SPECIAL_BREAK)); + setTimerDuration(secondBreak.duration); + notification( + "Special break started.", + { + body: `Enjoy your ${secondBreak.duration} ${ + isEqualToOne(secondBreak.duration) + ? "minute" + : "minutes" + } special break.`, + }, + specialBreakStartedWav + ); + return; + } + + if (thirdBreak && currentTime === thirdBreak.fromTime) { + dispatch(setTimerType(TimerStatus.SPECIAL_BREAK)); + setTimerDuration(thirdBreak.duration); + notification( + "Special break started.", + { + body: `Enjoy your ${thirdBreak.duration} ${ + isEqualToOne(thirdBreak.duration) + ? "minute" + : "minutes" + } special break.`, + }, + specialBreakStartedWav + ); + return; + } + + if (fourthBreak && currentTime === fourthBreak.fromTime) { + dispatch(setTimerType(TimerStatus.SPECIAL_BREAK)); + setTimerDuration(fourthBreak.duration); + notification( + "Special break started.", + { + body: `Enjoy your ${fourthBreak.duration} ${ + isEqualToOne(fourthBreak.duration) + ? "minute" + : "minutes" + } special break.`, + }, + specialBreakStartedWav + ); + return; } } else { return clearInterval(interval); @@ -200,13 +191,13 @@ const CounterProvider: React.FC = ({ children }) => { useEffect(() => { switch (timer.timerType) { - case STAY_FOCUS: + case TimerStatus.STAY_FOCUS: setTimerDuration(config.stayFocus); break; - case SHORT_BREAK: + case TimerStatus.SHORT_BREAK: setTimerDuration(config.shortBreak); break; - case LONG_BREAK: + case TimerStatus.LONG_BREAK: setTimerDuration(config.longBreak); break; } @@ -236,26 +227,29 @@ const CounterProvider: React.FC = ({ children }) => { useEffect(() => { if (settings.notificationType === "extra") { if (count === 61) { - if (timer.timerType === SHORT_BREAK) { + if (timer.timerType === TimerStatus.SHORT_BREAK) { notification( "60 seconds left.", { body: "Prepare yourself to stay focused again." }, settings.enableVoiceAssistance && sixtySecondsLeftWav ); - } else if (timer.timerType === LONG_BREAK) { + } else if (timer.timerType === TimerStatus.LONG_BREAK) { notification( "60 seconds left.", { body: "Prepare yourself to stay focused again." }, settings.enableVoiceAssistance && sixtySecondsLeftWav ); - } else if (timer.timerType === SPECIAL_BREAK) { + } else if (timer.timerType === TimerStatus.SPECIAL_BREAK) { notification( "60 seconds left.", { body: "Prepare yourself to stay focused again." }, settings.enableVoiceAssistance && sixtySecondsLeftWav ); } - } else if (count === 31 && timer.timerType === STAY_FOCUS) { + } else if ( + count === 31 && + timer.timerType === TimerStatus.STAY_FOCUS + ) { notification( "30 seconds left.", { body: "Pause all media playing if there's one." }, @@ -266,7 +260,7 @@ const CounterProvider: React.FC = ({ children }) => { if (count === 0) { switch (timer.timerType) { - case STAY_FOCUS: + case TimerStatus.STAY_FOCUS: if (timer.round < config.sessionRounds) { setTimeout(() => { notification( @@ -281,7 +275,7 @@ const CounterProvider: React.FC = ({ children }) => { settings.enableVoiceAssistance && focusFinishedWav ); - dispatch(setTimerType("SHORT_BREAK")); + dispatch(setTimerType(TimerStatus.SHORT_BREAK)); }, 1000); } else { setTimeout(() => { @@ -297,12 +291,12 @@ const CounterProvider: React.FC = ({ children }) => { settings.enableVoiceAssistance && sessionCompletedWav ); - dispatch(setTimerType("LONG_BREAK")); + dispatch(setTimerType(TimerStatus.LONG_BREAK)); }, 1000); } break; - case SHORT_BREAK: + case TimerStatus.SHORT_BREAK: setTimeout(() => { notification( "Break time finished.", @@ -316,7 +310,7 @@ const CounterProvider: React.FC = ({ children }) => { settings.enableVoiceAssistance && breakFinishedWav ); - dispatch(setTimerType("STAY_FOCUS")); + dispatch(setTimerType(TimerStatus.STAY_FOCUS)); dispatch(setRound(timer.round + 1)); if (!settings.autoStartWorkTime) { @@ -325,7 +319,7 @@ const CounterProvider: React.FC = ({ children }) => { }, 1000); break; - case LONG_BREAK: + case TimerStatus.LONG_BREAK: setTimeout(() => { notification( "Break time finished.", @@ -339,7 +333,7 @@ const CounterProvider: React.FC = ({ children }) => { settings.enableVoiceAssistance && breakFinishedWav ); - dispatch(setTimerType("STAY_FOCUS")); + dispatch(setTimerType(TimerStatus.STAY_FOCUS)); dispatch(setRound(1)); if (!settings.autoStartWorkTime) { @@ -348,7 +342,7 @@ const CounterProvider: React.FC = ({ children }) => { }, 1000); break; - case SPECIAL_BREAK: + case TimerStatus.SPECIAL_BREAK: setTimeout(() => { notification( "Break time finished.", @@ -362,7 +356,7 @@ const CounterProvider: React.FC = ({ children }) => { settings.enableVoiceAssistance && breakFinishedWav ); - dispatch(setTimerType("STAY_FOCUS")); + dispatch(setTimerType(TimerStatus.STAY_FOCUS)); if (!settings.autoStartWorkTime) { dispatch(setPlay(false)); @@ -389,7 +383,7 @@ const CounterProvider: React.FC = ({ children }) => { useEffect(() => { if (settings.enableFullscreenBreak) { - if (timer.timerType !== STAY_FOCUS) { + if (timer.timerType !== TimerStatus.STAY_FOCUS) { setShouldFullscreen(true); } else { setShouldFullscreen(false); diff --git a/app/renderer/src/contexts/ThemeContext.tsx b/app/renderer/src/contexts/ThemeContext.tsx index 453bed5f..ce3cccde 100644 --- a/app/renderer/src/contexts/ThemeContext.tsx +++ b/app/renderer/src/contexts/ThemeContext.tsx @@ -1,8 +1,8 @@ import React, { useRef } from "react"; import { isPreferredDark } from "utils"; import { GlobalStyles } from "styles"; -import { useSelector, useDispatch } from "react-redux"; -import { AppStateTypes, setEnableDarkTheme, SettingTypes } from "store"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; +import { setEnableDarkTheme } from "store"; type ThemeProps = { isDarkMode: boolean; @@ -14,11 +14,9 @@ const ThemeContext = React.createContext({ }); const ThemeProvider: React.FC = ({ children }) => { - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const useNativeTitlebar = useRef(settings.useNativeTitlebar); diff --git a/app/renderer/src/contexts/connectors/ElectronConnector.tsx b/app/renderer/src/contexts/connectors/ElectronConnector.tsx index 846f8460..c0d9f3ce 100644 --- a/app/renderer/src/contexts/connectors/ElectronConnector.tsx +++ b/app/renderer/src/contexts/connectors/ElectronConnector.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useContext, useEffect } from "react"; import { ConnnectorContext } from "../ConnnectorContext"; -import { useSelector } from "react-redux"; -import { AppStateTypes, SettingTypes } from "../../store"; +import { useAppSelector } from "hooks/storeHooks"; import { CounterContext } from "../CounterContext"; import { SET_ALWAYS_ON_TOP, @@ -29,11 +28,9 @@ export const ElectronInvokeConnector: InvokeConnector = { export const ElectronConnectorProvider: React.FC = ({ children }) => { const { electron } = window; - const timer = useSelector((state: AppStateTypes) => state.timer); + const timer = useAppSelector((state) => state.timer); - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); const { shouldFullscreen } = useContext(CounterContext); diff --git a/app/renderer/src/contexts/connectors/TauriConnector.tsx b/app/renderer/src/contexts/connectors/TauriConnector.tsx index 26d8522c..55f9ebd7 100644 --- a/app/renderer/src/contexts/connectors/TauriConnector.tsx +++ b/app/renderer/src/contexts/connectors/TauriConnector.tsx @@ -1,7 +1,6 @@ import React, { useCallback, useContext, useEffect } from "react"; import { ConnnectorContext } from "../ConnnectorContext"; -import { useDispatch, useSelector } from "react-redux"; -import { AppStateTypes, SettingTypes } from "../../store"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; import { CounterContext } from "../CounterContext"; import { CHECK_FOR_UPDATES, @@ -36,11 +35,9 @@ export const TauriInvokeConnector = { }; export const TauriConnectorProvider: React.FC = ({ children }) => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); // Prevent webpage behavior (naitive apps shouldn't refresh with F5 or Ctrl+R) useEffect(() => { @@ -114,7 +111,7 @@ export const TauriConnectorProvider: React.FC = ({ children }) => { }); }, [settings.openAtLogin]); - const timer = useSelector((state: AppStateTypes) => state.timer); + const timer = useAppSelector((state) => state.timer); const { shouldFullscreen } = useContext(CounterContext); diff --git a/app/renderer/src/hooks/storeHooks.ts b/app/renderer/src/hooks/storeHooks.ts new file mode 100644 index 00000000..1d0b4083 --- /dev/null +++ b/app/renderer/src/hooks/storeHooks.ts @@ -0,0 +1,10 @@ +import { + TypedUseSelectorHook, + useDispatch, + useSelector, +} from "react-redux"; +import type { AppStateTypes, AppDispatchTypes } from "store"; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = + useSelector; diff --git a/app/renderer/src/hooks/useTrayIconUpdates.tsx b/app/renderer/src/hooks/useTrayIconUpdates.tsx index 49ac8776..0dd11976 100644 --- a/app/renderer/src/hooks/useTrayIconUpdates.tsx +++ b/app/renderer/src/hooks/useTrayIconUpdates.tsx @@ -1,14 +1,13 @@ import { CounterContext } from "contexts"; import { useEffect, useContext } from "react"; -import { useSelector } from "react-redux"; -import type { AppStateTypes } from "store"; +import { useAppSelector } from "./storeHooks"; import { TraySVG } from "components"; import { encodeSvg } from "utils"; export const useTrayIconUpdates = ( onNewIcon: (dataUrl: string) => void ) => { - const timer = useSelector((state: AppStateTypes) => state.timer); + const timer = useAppSelector((state) => state.timer); const { count, duration, timerType } = useContext(CounterContext); const dashOffset = (duration - count) * (24 / duration); diff --git a/app/renderer/src/routes/Config/ConfigHeader.tsx b/app/renderer/src/routes/Config/ConfigHeader.tsx index 68da89a2..36b13fd5 100644 --- a/app/renderer/src/routes/Config/ConfigHeader.tsx +++ b/app/renderer/src/routes/Config/ConfigHeader.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState } from "react"; -import { useDispatch } from "react-redux"; +import { useAppDispatch } from "hooks/storeHooks"; import { restoreDefaultConfig } from "store"; import { StyledHeaderButton } from "styles"; import { Header } from "components"; const ConfigHeader: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [success, setSuccess] = useState(false); diff --git a/app/renderer/src/routes/Config/SliderSection.tsx b/app/renderer/src/routes/Config/SliderSection.tsx index 20b16c73..c93fd12b 100644 --- a/app/renderer/src/routes/Config/SliderSection.tsx +++ b/app/renderer/src/routes/Config/SliderSection.tsx @@ -1,7 +1,6 @@ import React, { useCallback } from "react"; -import { useSelector, useDispatch } from "react-redux"; +import { useAppSelector, useAppDispatch } from "hooks/storeHooks"; import { - AppStateTypes, setStayFocus, setSessionRounds, setShorBreak, @@ -11,16 +10,16 @@ import { StyledConfigSliderSection } from "styles"; import ConfigSlider, { ConfigSliderProps } from "./ConfigSlider"; const SliderSection: React.FC = () => { + const dispatch = useAppDispatch(); + const { stayFocus, shortBreak, longBreak, sessionRounds } = - useSelector(({ config }: AppStateTypes) => ({ + useAppSelector(({ config }) => ({ stayFocus: config.stayFocus, shortBreak: config.shortBreak, longBreak: config.longBreak, sessionRounds: config.sessionRounds, })); - const dispatch = useDispatch(); - const sliderRangeList: ConfigSliderProps[] = [ { label: "Stay focus", diff --git a/app/renderer/src/routes/Config/SpecialBreaks.tsx b/app/renderer/src/routes/Config/SpecialBreaks.tsx index 0734e1f3..ab71ef2e 100644 --- a/app/renderer/src/routes/Config/SpecialBreaks.tsx +++ b/app/renderer/src/routes/Config/SpecialBreaks.tsx @@ -1,7 +1,6 @@ import React, { useCallback } from "react"; -import { useSelector, useDispatch } from "react-redux"; +import { useAppSelector, useAppDispatch } from "hooks/storeHooks"; import { - AppStateTypes, setFirstSpecialBreak, setSecondSpecialBreak, setThirdSpecialBreak, @@ -15,9 +14,9 @@ import { import SpecialField from "./SpecialField"; const SpecialBreaks: React.FC = () => { - const config = useSelector((state: AppStateTypes) => state.config); + const config = useAppSelector((state) => state.config); - const dispath = useDispatch(); + const dispath = useAppDispatch(); const setFirstSpecialBreakCallback = useCallback( (values) => { diff --git a/app/renderer/src/routes/Config/SpecialField.tsx b/app/renderer/src/routes/Config/SpecialField.tsx index a394c336..d7c3506e 100644 --- a/app/renderer/src/routes/Config/SpecialField.tsx +++ b/app/renderer/src/routes/Config/SpecialField.tsx @@ -14,9 +14,9 @@ import { Time, SVG } from "components"; import { parseTime } from "utils"; type SpecialFieldProps = { - fromTime: string; - toTime: string; - duration: number; + fromTime?: string; + toTime?: string; + duration?: number; }; type Props = { @@ -88,6 +88,7 @@ const SpecialField: React.FC = ({ if ( values.fromTime && values.toTime && + values.duration && values.duration >= 5 && onFieldSubmit ) { @@ -218,7 +219,9 @@ const SpecialField: React.FC = ({ /> Duration:  - {values.duration < 5 && errors.duration ? ( + {!values.duration ? ( + "" + ) : (values.duration || 0) < 5 && errors.duration ? ( {values.duration > 1 ? `${values.duration} minutes` diff --git a/app/renderer/src/routes/Settings/FeatureSection.tsx b/app/renderer/src/routes/Settings/FeatureSection.tsx index cc71b244..89bbf89e 100644 --- a/app/renderer/src/routes/Settings/FeatureSection.tsx +++ b/app/renderer/src/routes/Settings/FeatureSection.tsx @@ -1,11 +1,9 @@ import React, { useCallback, useContext } from "react"; -import { useSelector, useDispatch } from "react-redux"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; import { setAlwaysOnTop, setEnableStrictMode, - AppStateTypes, setEnableProgressAnimation, - SettingTypes, setNotificationType, setEnableFullscreenBreak, setUseNativeTitlebar, @@ -21,13 +19,12 @@ import { ThemeContext } from "contexts"; import SettingSection from "./SettingSection"; import { detectOS } from "utils"; +import { NotificationTypes } from "store/settings/types"; const FeatureSection: React.FC = () => { - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const { isDarkMode, toggleThemeAction } = useContext(ThemeContext); @@ -145,7 +142,9 @@ const FeatureSection: React.FC = () => { const onChangeNotificationProps = useCallback( (e: React.ChangeEvent) => { - dispatch(setNotificationType(e.target.value)); + dispatch( + setNotificationType(e.target.value as NotificationTypes) + ); }, [dispatch] ); @@ -169,24 +168,28 @@ const FeatureSection: React.FC = () => { id="none" label="none" name="notification" - value="none" - checked={settings.notificationType === "none"} + value={NotificationTypes.NONE} + checked={settings.notificationType === NotificationTypes.NONE} onChange={onChangeNotificationProps} /> diff --git a/app/renderer/src/routes/Settings/SettingHeader.tsx b/app/renderer/src/routes/Settings/SettingHeader.tsx index 34c3c16f..769efe79 100644 --- a/app/renderer/src/routes/Settings/SettingHeader.tsx +++ b/app/renderer/src/routes/Settings/SettingHeader.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState } from "react"; import { restoreDefaultSettings } from "store"; -import { useDispatch } from "react-redux"; +import { useAppDispatch } from "hooks/storeHooks"; import { Header } from "components"; import { StyledHeaderButton } from "styles"; const SettingHeader: React.FC = () => { - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [success, setSuccess] = useState(false); diff --git a/app/renderer/src/routes/Settings/index.tsx b/app/renderer/src/routes/Settings/index.tsx index 9b765c4f..9357accb 100644 --- a/app/renderer/src/routes/Settings/index.tsx +++ b/app/renderer/src/routes/Settings/index.tsx @@ -8,17 +8,13 @@ import HelpSection from "./HelpSection"; import ShortcutSection from "./ShortcutSection"; import StickySection from "./StickySection"; import SettingHeader from "./SettingHeader"; -import { AppStateTypes, SettingTypes } from "../../store"; -import { useSelector } from "react-redux"; -import { UpdateTypes } from "../../store/update"; +import { useAppSelector } from "hooks/storeHooks"; import { Updater } from "../../components"; export default function Settings() { const alertState = getFromStorage("alert") || null; - const update: UpdateTypes = useSelector( - (state: AppStateTypes) => state.update - ); + const update = useAppSelector((state) => state.update); const [alert, setAlert] = useState(alertState); diff --git a/app/renderer/src/routes/Timer/Control/CompactModeButton.tsx b/app/renderer/src/routes/Timer/Control/CompactModeButton.tsx index db9b6a38..aadf12ab 100644 --- a/app/renderer/src/routes/Timer/Control/CompactModeButton.tsx +++ b/app/renderer/src/routes/Timer/Control/CompactModeButton.tsx @@ -1,17 +1,14 @@ import { SVG } from "components"; import { CounterContext } from "contexts"; import { useRippleEffect } from "hooks"; +import { useAppSelector } from "hooks/storeHooks"; import React, { useCallback, useRef } from "react"; -import { useSelector } from "react-redux"; -import { AppStateTypes, SettingTypes } from "store"; import { StyledCompactButton } from "styles"; type Props = { flipped?: boolean } & React.HTMLProps; const CompactModeButton: React.FC = ({ onClick, flipped }) => { - const { compactMode }: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const { compactMode } = useAppSelector((state) => state.settings); const buttonRef = useRef(null); diff --git a/app/renderer/src/routes/Timer/Control/Control.tsx b/app/renderer/src/routes/Timer/Control/Control.tsx index f95a6cfd..6f1bb82a 100644 --- a/app/renderer/src/routes/Timer/Control/Control.tsx +++ b/app/renderer/src/routes/Timer/Control/Control.tsx @@ -1,19 +1,14 @@ import WarningBell from "assets/audios/warning-bell.wav"; import { SVG } from "components"; import React, { useCallback, useEffect, useState } from "react"; -import { useDispatch, useSelector } from "react-redux"; +import { useAppDispatch, useAppSelector } from "hooks/storeHooks"; +import { TimerStatus } from "store/timer/types"; import { - AppStateTypes, - LONG_BREAK, setEnableCompactMode, setPlay, setRound, setTimerType, - SettingTypes, - SHORT_BREAK, skipTimer, - SPECIAL_BREAK, - STAY_FOCUS, toggleNotificationSound, } from "store"; import { @@ -35,16 +30,14 @@ type Props = { }; const Control: React.FC = ({ resetTimerAction }) => { - const { timer, config } = useSelector((state: AppStateTypes) => ({ + const { timer, config } = useAppSelector((state) => ({ timer: state.timer, config: state.config, })); - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const [warn, setWarn] = useState(false); @@ -98,29 +91,29 @@ const Control: React.FC = ({ resetTimerAction }) => { } switch (timer.timerType) { - case STAY_FOCUS: + case TimerStatus.STAY_FOCUS: if (timer.round < config.sessionRounds) { - dispatch(skipTimer("SHORT_BREAK")); + dispatch(skipTimer(TimerStatus.SHORT_BREAK)); } else { - dispatch(skipTimer("LONG_BREAK")); + dispatch(skipTimer(TimerStatus.LONG_BREAK)); } if (!timer.playing) dispatch(setPlay(!timer.playing)); break; - case SHORT_BREAK: - dispatch(skipTimer("STAY_FOCUS")); + case TimerStatus.SHORT_BREAK: + dispatch(skipTimer(TimerStatus.STAY_FOCUS)); dispatch(setRound(timer.round + 1)); if (!timer.playing) dispatch(setPlay(!timer.playing)); break; - case LONG_BREAK: - dispatch(skipTimer("STAY_FOCUS")); + case TimerStatus.LONG_BREAK: + dispatch(skipTimer(TimerStatus.STAY_FOCUS)); dispatch(setRound(1)); if (!timer.playing) dispatch(setPlay(!timer.playing)); break; - case SPECIAL_BREAK: - dispatch(skipTimer("STAY_FOCUS")); + case TimerStatus.SPECIAL_BREAK: + dispatch(skipTimer(TimerStatus.STAY_FOCUS)); if (!timer.playing) dispatch(setPlay(!timer.playing)); break; } @@ -135,7 +128,7 @@ const Control: React.FC = ({ resetTimerAction }) => { ]); const onResetSessionCallback = useCallback(() => { - dispatch(setTimerType("STAY_FOCUS")); + dispatch(setTimerType(TimerStatus.STAY_FOCUS)); dispatch(setRound(1)); }, [dispatch]); diff --git a/app/renderer/src/routes/Timer/Control/Sessions.tsx b/app/renderer/src/routes/Timer/Control/Sessions.tsx index e79e5290..eedb1d5b 100644 --- a/app/renderer/src/routes/Timer/Control/Sessions.tsx +++ b/app/renderer/src/routes/Timer/Control/Sessions.tsx @@ -1,10 +1,10 @@ import React from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; import { StyledSessions, StyledSessionReset } from "styles"; import { SVG } from "components"; type Props = { - timerType: TimerTypes["timerType"]; + timerType: TimerStatus; round: number; sessionRounds: number; onClick?: diff --git a/app/renderer/src/routes/Timer/Counter/Counter.tsx b/app/renderer/src/routes/Timer/Counter/Counter.tsx index 13861cad..d7aadf42 100644 --- a/app/renderer/src/routes/Timer/Counter/Counter.tsx +++ b/app/renderer/src/routes/Timer/Counter/Counter.tsx @@ -1,8 +1,7 @@ import { CounterContext } from "contexts"; import { useTime } from "hooks"; import React, { useContext } from "react"; -import { useSelector } from "react-redux"; -import { AppStateTypes, SettingTypes } from "store"; +import { useAppSelector } from "hooks/storeHooks"; import { StyledCounterContainer, StyledCounterProgress, @@ -13,9 +12,7 @@ import CounterTimer from "./CounterTimer"; import CounterType from "./CounterType"; const Counter: React.FC = () => { - const settings: SettingTypes = useSelector( - (state: AppStateTypes) => state.settings - ); + const settings = useAppSelector((state) => state.settings); const { count, duration, timerType, shouldFullscreen } = useContext(CounterContext); diff --git a/app/renderer/src/routes/Timer/Counter/CounterLabel.tsx b/app/renderer/src/routes/Timer/Counter/CounterLabel.tsx index a66e6ad2..0064c63d 100644 --- a/app/renderer/src/routes/Timer/Counter/CounterLabel.tsx +++ b/app/renderer/src/routes/Timer/Counter/CounterLabel.tsx @@ -1,17 +1,17 @@ import React from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; import { StyledCounterLabel } from "styles"; type Props = { - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; }; const CounterLabel: React.FC = ({ timerType }) => { return ( - {(timerType === "SHORT_BREAK" && "Short Break") || - (timerType === "LONG_BREAK" && "Long Break") || - (timerType === "SPECIAL_BREAK" && "Special Break") || + {(timerType === TimerStatus.SHORT_BREAK && "Short Break") || + (timerType === TimerStatus.LONG_BREAK && "Long Break") || + (timerType === TimerStatus.SHORT_BREAK && "Special Break") || "Stay Focused"} ); diff --git a/app/renderer/src/routes/Timer/Counter/CounterTimer.tsx b/app/renderer/src/routes/Timer/Counter/CounterTimer.tsx index 40a441c9..9cb7bf0e 100644 --- a/app/renderer/src/routes/Timer/Counter/CounterTimer.tsx +++ b/app/renderer/src/routes/Timer/Counter/CounterTimer.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; import { StyledCounterTimer } from "styles"; type Props = { - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; hours: string; minutes: string; seconds: string; diff --git a/app/renderer/src/routes/Timer/Counter/CounterType.tsx b/app/renderer/src/routes/Timer/Counter/CounterType.tsx index de31e0f9..de1814eb 100644 --- a/app/renderer/src/routes/Timer/Counter/CounterType.tsx +++ b/app/renderer/src/routes/Timer/Counter/CounterType.tsx @@ -1,20 +1,20 @@ import React from "react"; -import { TimerTypes } from "store"; +import { TimerStatus } from "store/timer/types"; import { StyledCounterType } from "styles"; import { SVG } from "components"; type Props = { - timerType?: TimerTypes["timerType"]; + timerType?: TimerStatus; }; const CounterType: React.FC = ({ timerType }) => { return ( - {(timerType === "SHORT_BREAK" && ) || - (timerType === "LONG_BREAK" && ) || - (timerType === "SPECIAL_BREAK" && ) || ( - - )} + {(timerType === TimerStatus.SHORT_BREAK && ) || + (timerType === TimerStatus.LONG_BREAK && ) || + (timerType === TimerStatus.SPECIAL_BREAK && ( + + )) || } ); }; diff --git a/app/renderer/src/routes/Timer/index.tsx b/app/renderer/src/routes/Timer/index.tsx index 016568d3..dd76c124 100644 --- a/app/renderer/src/routes/Timer/index.tsx +++ b/app/renderer/src/routes/Timer/index.tsx @@ -1,15 +1,14 @@ import { CounterContext } from "contexts"; import React, { useCallback, useContext } from "react"; -import { useSelector } from "react-redux"; -import { AppStateTypes } from "store"; +import { useAppSelector } from "hooks/storeHooks"; import { StyledTimer } from "styles"; import Control from "./Control"; import Counter from "./Counter"; import PriorityCard from "./PriorityCard"; export default function Timer() { - const compactMode = useSelector( - (state: AppStateTypes) => state.settings.compactMode + const compactMode = useAppSelector( + (state) => state.settings.compactMode ); const { resetTimerAction } = useContext(CounterContext); diff --git a/app/renderer/src/store/config/actions.ts b/app/renderer/src/store/config/actions.ts deleted file mode 100644 index 112c0cd7..00000000 --- a/app/renderer/src/store/config/actions.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - ConfigTypes, - ConfigActionTypes, - SET_STAY_FOCUS, - SET_SHORT_BREAK, - SET_LONG_BREAK, - SET_SESSION_ROUNDS, - RESTORE_DEFAULT_CONFIG, - SpecialBreakTypes, - SET_FIRST_SPECIAL_BREAK, - SET_SECOND_SPECIAL_BREAK, - SET_THIRD_SPECIAL_BREAK, - SET_FOUTH_SPECIAL_BREAK, -} from "./types"; - -export const setStayFocus = ( - stayFocus: ConfigTypes["stayFocus"] -): ConfigActionTypes => { - return { - type: SET_STAY_FOCUS, - payload: stayFocus, - }; -}; - -export const setShorBreak = ( - shortBreak: ConfigTypes["shortBreak"] -): ConfigActionTypes => { - return { - type: SET_SHORT_BREAK, - payload: shortBreak, - }; -}; - -export const setLongBreak = ( - longBreak: ConfigTypes["longBreak"] -): ConfigActionTypes => { - return { - type: SET_LONG_BREAK, - payload: longBreak, - }; -}; - -export const setSessionRounds = ( - sessionRounds: ConfigTypes["sessionRounds"] -): ConfigActionTypes => { - return { - type: SET_SESSION_ROUNDS, - payload: sessionRounds, - }; -}; - -export const restoreDefaultConfig = () => ({ - type: RESTORE_DEFAULT_CONFIG, -}); - -export const setFirstSpecialBreak = ( - specialBreak: SpecialBreakTypes -): ConfigActionTypes => { - return { - type: SET_FIRST_SPECIAL_BREAK, - payload: specialBreak, - }; -}; - -export const setSecondSpecialBreak = ( - specialBreak: SpecialBreakTypes -): ConfigActionTypes => { - return { - type: SET_SECOND_SPECIAL_BREAK, - payload: specialBreak, - }; -}; - -export const setThirdSpecialBreak = ( - specialBreak: SpecialBreakTypes -): ConfigActionTypes => { - return { - type: SET_THIRD_SPECIAL_BREAK, - payload: specialBreak, - }; -}; - -export const setFourthSpecialBreak = ( - specialBreak: SpecialBreakTypes -): ConfigActionTypes => { - return { - type: SET_FOUTH_SPECIAL_BREAK, - payload: specialBreak, - }; -}; diff --git a/app/renderer/src/store/config/defaultConfig.ts b/app/renderer/src/store/config/defaultConfig.ts new file mode 100644 index 00000000..162251e5 --- /dev/null +++ b/app/renderer/src/store/config/defaultConfig.ts @@ -0,0 +1,30 @@ +import { ConfigTypes } from "./types"; + +export const defaultConfig: Readonly = Object.freeze({ + stayFocus: 25, + shortBreak: 5, + longBreak: 15, + sessionRounds: 4, + specialBreaks: { + firstBreak: { + fromTime: "", + toTime: "", + duration: 0, + }, + secondBreak: { + fromTime: "", + toTime: "", + duration: 0, + }, + thirdBreak: { + fromTime: "", + toTime: "", + duration: 0, + }, + fourthBreak: { + fromTime: "", + toTime: "", + duration: 0, + }, + }, +}); diff --git a/app/renderer/src/store/config/index.ts b/app/renderer/src/store/config/index.ts index 6d30c416..a8bfa87a 100644 --- a/app/renderer/src/store/config/index.ts +++ b/app/renderer/src/store/config/index.ts @@ -1,3 +1,78 @@ -export * from "./actions"; -export * from "./reducer"; -export * from "./types"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { getFromStorage } from "utils"; +import { ConfigPayload, ConfigTypes } from "./types"; +import { defaultConfig } from "./defaultConfig"; + +const config = + (getFromStorage("state") && getFromStorage("state").config) || + defaultConfig; + +const initialState: ConfigTypes = config; + +const configSlice = createSlice({ + name: "config", + initialState, + reducers: { + setStayFocus(state, action: ConfigPayload<"stayFocus">) { + state.stayFocus = action.payload; + }, + + setShorBreak(state, action: ConfigPayload<"shortBreak">) { + state.shortBreak = action.payload; + }, + + setLongBreak(state, action: ConfigPayload<"longBreak">) { + state.longBreak = action.payload; + }, + + setSessionRounds(state, action: ConfigPayload<"sessionRounds">) { + state.sessionRounds = action.payload; + }, + + restoreDefaultConfig() { + return defaultConfig; + }, + + setFirstSpecialBreak( + state, + action: PayloadAction + ) { + state.specialBreaks.firstBreak = action.payload; + }, + + setSecondSpecialBreak( + state, + action: PayloadAction + ) { + state.specialBreaks.secondBreak = action.payload; + }, + + setThirdSpecialBreak( + state, + action: PayloadAction + ) { + state.specialBreaks.thirdBreak = action.payload; + }, + + setFourthSpecialBreak( + state, + action: PayloadAction + ) { + state.specialBreaks.fourthBreak = action.payload; + }, + }, +}); + +export const { + restoreDefaultConfig, + setFourthSpecialBreak, + setLongBreak, + setSecondSpecialBreak, + setSessionRounds, + setShorBreak, + setStayFocus, + setThirdSpecialBreak, + setFirstSpecialBreak, +} = configSlice.actions; + +export default configSlice.reducer; diff --git a/app/renderer/src/store/config/reducer.ts b/app/renderer/src/store/config/reducer.ts deleted file mode 100644 index 1d1493c1..00000000 --- a/app/renderer/src/store/config/reducer.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { getFromStorage } from "utils"; -import { - ConfigTypes, - ConfigActionTypes, - SET_STAY_FOCUS, - SET_SHORT_BREAK, - SET_LONG_BREAK, - SET_SESSION_ROUNDS, - RESTORE_DEFAULT_CONFIG, - SET_FIRST_SPECIAL_BREAK, - SET_SECOND_SPECIAL_BREAK, - SET_THIRD_SPECIAL_BREAK, - SET_FOUTH_SPECIAL_BREAK, -} from "./types"; - -const defaultConfig: ConfigTypes = { - stayFocus: 25, - shortBreak: 5, - longBreak: 15, - sessionRounds: 4, - specialBreaks: { - firstBreak: { - fromTime: "", - toTime: "", - duration: 0, - }, - secondBreak: { - fromTime: "", - toTime: "", - duration: 0, - }, - thirdBreak: { - fromTime: "", - toTime: "", - duration: 0, - }, - fourthBreak: { - fromTime: "", - toTime: "", - duration: 0, - }, - }, -}; - -const config = - (getFromStorage("state") && getFromStorage("state").config) || - defaultConfig; - -const initialState: ConfigTypes = config; - -export const configReducer = ( - state = initialState, - action: ConfigActionTypes -) => { - switch (action.type) { - case SET_STAY_FOCUS: - return { - ...state, - stayFocus: action.payload, - }; - case SET_SHORT_BREAK: - return { - ...state, - shortBreak: action.payload, - }; - case SET_LONG_BREAK: - return { - ...state, - longBreak: action.payload, - }; - case SET_SESSION_ROUNDS: - return { - ...state, - sessionRounds: action.payload, - }; - case SET_FIRST_SPECIAL_BREAK: - return { - ...state, - specialBreaks: { - ...state.specialBreaks, - firstBreak: action.payload, - }, - }; - case SET_SECOND_SPECIAL_BREAK: - return { - ...state, - specialBreaks: { - ...state.specialBreaks, - secondBreak: action.payload, - }, - }; - case SET_THIRD_SPECIAL_BREAK: - return { - ...state, - specialBreaks: { - ...state.specialBreaks, - thirdBreak: action.payload, - }, - }; - case SET_FOUTH_SPECIAL_BREAK: - return { - ...state, - specialBreaks: { - ...state.specialBreaks, - fourthBreak: action.payload, - }, - }; - case RESTORE_DEFAULT_CONFIG: - return defaultConfig; - default: - return state; - } -}; diff --git a/app/renderer/src/store/config/types.ts b/app/renderer/src/store/config/types.ts index de186b02..ee613583 100644 --- a/app/renderer/src/store/config/types.ts +++ b/app/renderer/src/store/config/types.ts @@ -1,4 +1,4 @@ -const config = "[config]"; +import { PayloadAction } from "@reduxjs/toolkit"; export type SpecialBreakTypes = { fromTime: string; @@ -19,63 +19,6 @@ export type ConfigTypes = { }; }; -export const SET_STAY_FOCUS = `${config} SET_STAY_FOCUS`; -export const SET_SHORT_BREAK = `${config} SET_SHORT_BREAK`; -export const SET_LONG_BREAK = `${config} SET_LONG_BREAK`; -export const SET_SESSION_ROUNDS = `${config} SET_SESSION_ROUNDS`; -export const RESTORE_DEFAULT_CONFIG = `${config} RESTORE_DEFAULT_CONFIG`; - -export const SET_FIRST_SPECIAL_BREAK = `${config} SET_FIRST_SPECIAL_BREAK`; -export const SET_SECOND_SPECIAL_BREAK = `${config} SET_SECOND_SPECIAL_BREAK`; -export const SET_THIRD_SPECIAL_BREAK = `${config} SET_THIRD_SPECIAL_BREAK`; -export const SET_FOUTH_SPECIAL_BREAK = `${config} SET_FOUTH_SPECIAL_BREAK`; - -interface SetStayFocus { - type: typeof SET_STAY_FOCUS; - payload: ConfigTypes["stayFocus"]; -} - -interface SetShortBreak { - type: typeof SET_SHORT_BREAK; - payload: ConfigTypes["shortBreak"]; -} - -interface SetLongBreak { - type: typeof SET_LONG_BREAK; - payload: ConfigTypes["longBreak"]; -} - -interface SetSessionRounds { - type: typeof SET_SESSION_ROUNDS; - payload: ConfigTypes["sessionRounds"]; -} - -interface SetFirstSpecialBreak { - type: typeof SET_FIRST_SPECIAL_BREAK; - payload: any; -} - -interface SetSecondSpecialBreak { - type: typeof SET_FIRST_SPECIAL_BREAK; - payload: any; -} - -interface SetThirdSpecialBreak { - type: typeof SET_FIRST_SPECIAL_BREAK; - payload: any; -} - -interface SetFourthSpecialBreak { - type: typeof SET_FIRST_SPECIAL_BREAK; - payload: any; -} - -export type ConfigActionTypes = - | SetStayFocus - | SetShortBreak - | SetLongBreak - | SetSessionRounds - | SetFirstSpecialBreak - | SetSecondSpecialBreak - | SetThirdSpecialBreak - | SetFourthSpecialBreak; +export type ConfigPayload = PayloadAction< + ConfigTypes[T] +>; diff --git a/app/renderer/src/store/settings/actions.ts b/app/renderer/src/store/settings/actions.ts deleted file mode 100644 index 5d0097c3..00000000 --- a/app/renderer/src/store/settings/actions.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { - SettingTypes, - SettingActionTypes, - IGNORE_UPDATE, - ALWAYS_ON_TOP, - RESTORE_DEFAULT_SETTINGS, - ENABLE_DARK_THEME, - TOGGLE_NOTIFICATION_SOUND, - ENABLE_PROGRESS_ANIMATION, - SET_NOTIFICATION_PROPERTY, - ENABLE_FULLSCREEN_BREAK, - USE_NATIVE_TITLE_BAR, - ENABLE_STRICT_MODE, - CLOSE_TO_TRAY, - MINIMIZE_TO_TRAY, - AUTO_START_WORK_TIME, - ENABLE_VOICE_ASSISTANCE, - ENABLE_COMPACT_MODE, - OPEN_AT_LOGIN, -} from "./types"; - -export const setIgnoreUpdate = ( - ignoreUpdate: SettingTypes["ignoreUpdate"] -): SettingActionTypes => { - return { - type: IGNORE_UPDATE, - payload: ignoreUpdate, - }; -}; - -export const setAlwaysOnTop = ( - alwaysOnTop: SettingTypes["alwaysOnTop"] -): SettingActionTypes => { - return { - type: ALWAYS_ON_TOP, - payload: alwaysOnTop, - }; -}; - -export const toggleNotificationSound = () => { - return { - type: TOGGLE_NOTIFICATION_SOUND, - }; -}; - -export const setEnableDarkTheme = ( - enableDarkTheme: SettingTypes["enableDarkTheme"] -): SettingActionTypes => { - return { - type: ENABLE_DARK_THEME, - payload: enableDarkTheme, - }; -}; - -export const setEnableCompactMode = ( - enableCompactMode: SettingTypes["compactMode"] -): SettingActionTypes => { - return { - type: ENABLE_COMPACT_MODE, - payload: enableCompactMode, - }; -}; - -export const setEnableFullscreenBreak = ( - enableFullscreenBreak: SettingTypes["enableFullscreenBreak"] -): SettingActionTypes => { - return { - type: ENABLE_FULLSCREEN_BREAK, - payload: enableFullscreenBreak, - }; -}; - -export const setEnableStrictMode = ( - enableStrictMode: SettingTypes["enableStrictMode"] -): SettingActionTypes => { - return { - type: ENABLE_STRICT_MODE, - payload: enableStrictMode, - }; -}; - -export const setEnableProgressAnimation = ( - enableProgressAnimation: SettingTypes["enableProgressAnimation"] -): SettingActionTypes => { - return { - type: ENABLE_PROGRESS_ANIMATION, - payload: enableProgressAnimation, - }; -}; - -export const setEnableVoiceAssistance = ( - enableVoiceAssistance: SettingTypes["enableVoiceAssistance"] -): SettingActionTypes => { - return { - type: ENABLE_VOICE_ASSISTANCE, - payload: enableVoiceAssistance, - }; -}; - -export const setUseNativeTitlebar = ( - useNativeTitlebar: SettingTypes["useNativeTitlebar"] -): SettingActionTypes => { - return { - type: USE_NATIVE_TITLE_BAR, - payload: useNativeTitlebar, - }; -}; - -export const setNotificationType = ( - notificationType: string -): SettingActionTypes => { - return { - type: SET_NOTIFICATION_PROPERTY, - payload: notificationType, - }; -}; - -export const setCloseToTray = ( - closeToTray: SettingTypes["closeToTray"] -): SettingActionTypes => { - return { - type: CLOSE_TO_TRAY, - payload: closeToTray, - }; -}; - -export const setMinimizeToTray = ( - minimizeToTray: SettingTypes["minimizeToTray"] -): SettingActionTypes => { - return { - type: MINIMIZE_TO_TRAY, - payload: minimizeToTray, - }; -}; - -export const setAutoStartWorkTime = ( - autoStartWorkTime: SettingTypes["autoStartWorkTime"] -): SettingActionTypes => { - return { - type: AUTO_START_WORK_TIME, - payload: autoStartWorkTime, - }; -}; - -export const setOpenAtLogin = ( - openAtLogin: SettingTypes["openAtLogin"] -): SettingActionTypes => { - return { - type: OPEN_AT_LOGIN, - payload: openAtLogin, - }; -}; - -export const restoreDefaultSettings = () => ({ - type: RESTORE_DEFAULT_SETTINGS, -}); diff --git a/app/renderer/src/store/settings/defaultSettings.ts b/app/renderer/src/store/settings/defaultSettings.ts new file mode 100644 index 00000000..d8c2de55 --- /dev/null +++ b/app/renderer/src/store/settings/defaultSettings.ts @@ -0,0 +1,20 @@ +import { NotificationTypes, SettingTypes } from "./types"; +import { detectOS, isPreferredDark } from "utils"; + +export const defaultSettings: Readonly = Object.freeze({ + alwaysOnTop: false, + compactMode: false, + ignoreUpdate: "", + enableFullscreenBreak: false, + enableStrictMode: false, + enableDarkTheme: isPreferredDark(), + enableProgressAnimation: true, + enableVoiceAssistance: false, + notificationSoundOn: true, + notificationType: NotificationTypes.NONE, + closeToTray: true, + minimizeToTray: false, + autoStartWorkTime: false, + useNativeTitlebar: detectOS() === "Windows" ? false : true, + openAtLogin: false, +}); diff --git a/app/renderer/src/store/settings/index.ts b/app/renderer/src/store/settings/index.ts index 6d30c416..a7de5d0b 100644 --- a/app/renderer/src/store/settings/index.ts +++ b/app/renderer/src/store/settings/index.ts @@ -1,3 +1,133 @@ -export * from "./actions"; -export * from "./reducer"; -export * from "./types"; +import { createSlice } from "@reduxjs/toolkit"; +import { getFromStorage } from "utils"; +import { SettingTypes, SettingsPayload } from "./types"; +import { defaultSettings } from "./defaultSettings"; + +export type { SettingTypes }; + +const settings = + (getFromStorage("state") && getFromStorage("state").settings) || + defaultSettings; + +const initialState: SettingTypes = settings; + +const settingsSlice = createSlice({ + name: "settings", + initialState, + reducers: { + setIgnoreUpdate(state, action: SettingsPayload<"ignoreUpdate">) { + state.ignoreUpdate = action.payload; + }, + + setAlwaysOnTop(state, action: SettingsPayload<"alwaysOnTop">) { + state.alwaysOnTop = action.payload; + }, + + toggleNotificationSound(state) { + state.notificationSoundOn = !state.notificationSoundOn; + }, + + setEnableDarkTheme( + state, + action: SettingsPayload<"enableDarkTheme"> + ) { + state.enableDarkTheme = action.payload; + }, + + setEnableCompactMode( + state, + action: SettingsPayload<"compactMode"> + ) { + state.compactMode = action.payload; + }, + + setEnableFullscreenBreak( + state, + action: SettingsPayload<"enableFullscreenBreak"> + ) { + state.enableFullscreenBreak = action.payload; + }, + + setEnableStrictMode( + state, + action: SettingsPayload<"enableStrictMode"> + ) { + state.enableStrictMode = action.payload; + }, + + setEnableProgressAnimation( + state, + action: SettingsPayload<"enableProgressAnimation"> + ) { + state.enableProgressAnimation = action.payload; + }, + + setEnableVoiceAssistance( + state, + action: SettingsPayload<"enableVoiceAssistance"> + ) { + state.enableVoiceAssistance = action.payload; + }, + + setUseNativeTitlebar( + state, + action: SettingsPayload<"useNativeTitlebar"> + ) { + state.useNativeTitlebar = action.payload; + }, + + setNotificationType( + state, + action: SettingsPayload<"notificationType"> + ) { + state.notificationType = action.payload; + }, + + setCloseToTray(state, action: SettingsPayload<"closeToTray">) { + state.closeToTray = action.payload; + }, + + setMinimizeToTray( + state, + action: SettingsPayload<"minimizeToTray"> + ) { + state.minimizeToTray = action.payload; + }, + + setAutoStartWorkTime( + state, + action: SettingsPayload<"autoStartWorkTime"> + ) { + state.autoStartWorkTime = action.payload; + }, + + setOpenAtLogin(state, action: SettingsPayload<"openAtLogin">) { + state.openAtLogin = action.payload; + }, + + restoreDefaultSettings() { + return defaultSettings; + }, + }, +}); + +export const { + restoreDefaultSettings, + setAlwaysOnTop, + setAutoStartWorkTime, + setCloseToTray, + setEnableCompactMode, + setEnableDarkTheme, + setEnableFullscreenBreak, + setEnableProgressAnimation, + setEnableStrictMode, + setEnableVoiceAssistance, + setIgnoreUpdate, + setMinimizeToTray, + setNotificationType, + setOpenAtLogin, + setUseNativeTitlebar, + toggleNotificationSound, +} = settingsSlice.actions; + +export default settingsSlice.reducer; diff --git a/app/renderer/src/store/settings/reducer.ts b/app/renderer/src/store/settings/reducer.ts deleted file mode 100644 index 8b7d99a3..00000000 --- a/app/renderer/src/store/settings/reducer.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { getFromStorage, isPreferredDark, detectOS } from "utils"; -import { - SettingTypes, - SettingActionTypes, - ALWAYS_ON_TOP, - ENABLE_DARK_THEME, - ENABLE_STRICT_MODE, - RESTORE_DEFAULT_SETTINGS, - TOGGLE_NOTIFICATION_SOUND, - SET_NOTIFICATION_PROPERTY, - ENABLE_FULLSCREEN_BREAK, - ENABLE_PROGRESS_ANIMATION, - USE_NATIVE_TITLE_BAR, - CLOSE_TO_TRAY, - MINIMIZE_TO_TRAY, - AUTO_START_WORK_TIME, - ENABLE_VOICE_ASSISTANCE, - ENABLE_COMPACT_MODE, - OPEN_AT_LOGIN, - IGNORE_UPDATE, -} from "./types"; - -const defaultSettings: SettingTypes = { - alwaysOnTop: false, - compactMode: false, - ignoreUpdate: "", - enableFullscreenBreak: false, - enableStrictMode: false, - enableDarkTheme: isPreferredDark(), - enableProgressAnimation: true, - enableVoiceAssistance: false, - notificationSoundOn: true, - notificationType: "none", - closeToTray: true, - minimizeToTray: false, - autoStartWorkTime: false, - useNativeTitlebar: detectOS() === "Windows" ? false : true, - openAtLogin: false, -}; - -const settings = - (getFromStorage("state") && getFromStorage("state").settings) || - defaultSettings; - -const initialState: SettingTypes = settings; - -export const settingReducer = ( - state = initialState, - action: SettingActionTypes -) => { - switch (action.type) { - case IGNORE_UPDATE: - return { - ...state, - ignoreUpdate: action.payload, - }; - case ALWAYS_ON_TOP: - return { - ...state, - alwaysOnTop: action.payload, - }; - case TOGGLE_NOTIFICATION_SOUND: - return { - ...state, - notificationSoundOn: !state.notificationSoundOn, - }; - case ENABLE_COMPACT_MODE: - return { - ...state, - compactMode: Boolean(action.payload), - }; - case ENABLE_FULLSCREEN_BREAK: - return { - ...state, - enableFullscreenBreak: action.payload, - }; - case ENABLE_DARK_THEME: - return { - ...state, - enableDarkTheme: action.payload, - }; - case ENABLE_STRICT_MODE: - return { - ...state, - enableStrictMode: action.payload, - }; - case ENABLE_PROGRESS_ANIMATION: - return { - ...state, - enableProgressAnimation: action.payload, - }; - case ENABLE_VOICE_ASSISTANCE: - return { - ...state, - enableVoiceAssistance: action.payload, - }; - case USE_NATIVE_TITLE_BAR: - return { - ...state, - useNativeTitlebar: action.payload, - }; - case SET_NOTIFICATION_PROPERTY: - return { - ...state, - notificationType: action.payload, - }; - case CLOSE_TO_TRAY: - return { - ...state, - closeToTray: action.payload, - }; - case MINIMIZE_TO_TRAY: - return { - ...state, - minimizeToTray: action.payload, - }; - case AUTO_START_WORK_TIME: - return { - ...state, - autoStartWorkTime: action.payload, - }; - case OPEN_AT_LOGIN: - return { - ...state, - openAtLogin: action.payload, - }; - case RESTORE_DEFAULT_SETTINGS: - return defaultSettings; - default: - return state; - } -}; diff --git a/app/renderer/src/store/settings/types.ts b/app/renderer/src/store/settings/types.ts index 225c88ba..90bf3443 100644 --- a/app/renderer/src/store/settings/types.ts +++ b/app/renderer/src/store/settings/types.ts @@ -1,4 +1,4 @@ -const settings = "[settings]"; +import { PayloadAction } from "@reduxjs/toolkit"; export type SettingTypes = { ignoreUpdate: string; @@ -14,39 +14,15 @@ export type SettingTypes = { closeToTray: boolean; minimizeToTray: boolean; autoStartWorkTime: boolean; - notificationType: "none" | "normal" | "extra"; + notificationType: NotificationTypes; openAtLogin: boolean; }; -/** - * the reason this is stored inside the settings and not the update is because we want to actually store this and remember it - */ -export const IGNORE_UPDATE = `${settings} IGNORE_UPDATE`; -export const ALWAYS_ON_TOP = `${settings} ALWAYS_ON_TOP`; -export const ENABLE_DARK_THEME = `${settings} ENABLE_DARK_THEME`; +export const enum NotificationTypes { + NONE = "none", + NORMAL = "normal", + EXTRA = "extra", +} -export const ENABLE_COMPACT_MODE = `${settings} ENABLE_COMPACT_MODE`; -export const ENABLE_FULLSCREEN_BREAK = `${settings} ENABLE_FULLSCREEN_BREAK`; -export const ENABLE_STRICT_MODE = `${settings} ENABLE_STRICT_MODE`; - -export const ENABLE_PROGRESS_ANIMATION = `${settings} ENABLE_PROGRESS_ANIMATION`; -export const ENABLE_VOICE_ASSISTANCE = `${settings} ENABLE_VOICE_ASSISTANCE`; - -export const USE_NATIVE_TITLE_BAR = `${settings} USE_NATIVE_TITLE_BAR`; -export const ENABLE_AUTO_UPDATES = `${settings} ENABLE_AUTO_UPDATES`; - -export const TOGGLE_NOTIFICATION_SOUND = `${settings} TOGGLE_NOTIFICATION_SOUND`; -export const SET_NOTIFICATION_PROPERTY = `${settings} SET_NOTIFICATION_PROPERTY`; - -export const CLOSE_TO_TRAY = `${settings} CLOSE_TO_TRAY`; -export const MINIMIZE_TO_TRAY = `${settings} MINIMIZE_TO_TRAY`; -export const AUTO_START_WORK_TIME = `${settings} AUTO_START_WORK_TIME`; - -export const OPEN_AT_LOGIN = `${settings} OPEN_AT_LOGIN`; - -export const RESTORE_DEFAULT_SETTINGS = `${settings} RESTORE_DEFAULT_SETTINGS`; - -export type SettingActionTypes = { - type: string; - payload: any; -}; +export type SettingsPayload = + PayloadAction; diff --git a/app/renderer/src/store/store.ts b/app/renderer/src/store/store.ts index 422ec887..5835055b 100644 --- a/app/renderer/src/store/store.ts +++ b/app/renderer/src/store/store.ts @@ -1,25 +1,25 @@ -import { createStore, combineReducers } from "redux"; -import { devToolsEnhancer } from "redux-devtools-extension"; +import { configureStore } from "@reduxjs/toolkit"; import debounce from "lodash.debounce"; import { saveToStorage, getFromStorage } from "utils"; -import { configReducer } from "./config"; -import { settingReducer } from "./settings"; -import { timerReducer } from "./timer"; +import configReducer from "./config"; +import settingReducer from "./settings"; +import timerReducer from "./timer"; import { undoableTasksReducer } from "./tasks"; -import { updateReducer } from "./update"; +import updateReducer from "./update"; -const rootReducer = combineReducers({ - config: configReducer, - settings: settingReducer, - timer: timerReducer, - tasks: undoableTasksReducer, - update: updateReducer, -}); - -export type AppStateTypes = ReturnType; +export type AppStateTypes = ReturnType; +export type AppDispatchTypes = typeof store.dispatch; -const store = createStore(rootReducer, devToolsEnhancer({})); +const store = configureStore({ + reducer: { + config: configReducer, + settings: settingReducer, + timer: timerReducer, + tasks: undoableTasksReducer, + update: updateReducer, + }, +}); if (!getFromStorage("state")) { saveToStorage("state", { diff --git a/app/renderer/src/store/timer/actions.ts b/app/renderer/src/store/timer/actions.ts deleted file mode 100644 index d4364368..00000000 --- a/app/renderer/src/store/timer/actions.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - TimerActionTypes, - SET_PLAY, - TimerTypes, - SET_TIMER_TYPE, - SET_ROUND, - SKIP_TIMER, -} from "./types"; - -export const setPlay = ( - playing: TimerTypes["playing"] -): TimerActionTypes => { - return { - type: SET_PLAY, - payload: playing, - }; -}; - -export const setTimerType = ( - timerType: TimerTypes["timerType"] -): TimerActionTypes => { - return { - type: SET_TIMER_TYPE, - payload: timerType, - }; -}; - -export const setRound = ( - round: TimerTypes["round"] -): TimerActionTypes => { - return { - type: SET_ROUND, - payload: round, - }; -}; - -export const skipTimer = ( - timerType: TimerTypes["timerType"] -): TimerActionTypes => { - return { - type: SKIP_TIMER, - payload: timerType, - }; -}; diff --git a/app/renderer/src/store/timer/defaultTimer.ts b/app/renderer/src/store/timer/defaultTimer.ts new file mode 100644 index 00000000..1d740dd3 --- /dev/null +++ b/app/renderer/src/store/timer/defaultTimer.ts @@ -0,0 +1,7 @@ +import { TimerStatus, TimerTypes } from "./types"; + +export const defaultTimer: Readonly = Object.freeze({ + round: 1, + playing: false, + timerType: TimerStatus.STAY_FOCUS, +}); diff --git a/app/renderer/src/store/timer/index.ts b/app/renderer/src/store/timer/index.ts index 6d30c416..d1ba560c 100644 --- a/app/renderer/src/store/timer/index.ts +++ b/app/renderer/src/store/timer/index.ts @@ -1,3 +1,39 @@ -export * from "./actions"; -export * from "./reducer"; -export * from "./types"; +import { createSlice } from "@reduxjs/toolkit"; +import { defaultTimer } from "./defaultTimer"; +import { TimerPayload } from "./types"; + +const timerSlice = createSlice({ + name: "timer", + initialState: defaultTimer, + reducers: { + setPlay(state, action: TimerPayload<"playing">) { + state.playing = action.payload; + }, + + setTimerType(state, action: TimerPayload<"timerType">) { + state.timerType = action.payload; + }, + + setRound(state, action: TimerPayload<"round">) { + state.round = action.payload; + }, + + skipTimer(state, action: TimerPayload<"timerType">) { + state.timerType = action.payload; + }, + + restartTimer() { + return defaultTimer; + }, + }, +}); + +export const { + setPlay, + setTimerType, + setRound, + skipTimer, + restartTimer, +} = timerSlice.actions; + +export default timerSlice.reducer; diff --git a/app/renderer/src/store/timer/reducer.ts b/app/renderer/src/store/timer/reducer.ts deleted file mode 100644 index dbb9d0fb..00000000 --- a/app/renderer/src/store/timer/reducer.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - TimerTypes, - TimerActionTypes, - SET_PLAY, - SET_TIMER_TYPE, - SET_ROUND, - SKIP_TIMER, -} from "./types"; - -const initialState: TimerTypes = { - round: 1, - playing: false, - timerType: "STAY_FOCUS", -}; - -export const timerReducer = ( - state = initialState, - action: TimerActionTypes -) => { - switch (action.type) { - case SET_PLAY: - return { - ...state, - playing: action.payload, - }; - case SET_TIMER_TYPE: - return { - ...state, - timerType: action.payload, - }; - case SET_ROUND: - return { - ...state, - round: action.payload, - }; - case SKIP_TIMER: - return { - ...state, - timerType: action.payload, - }; - default: - return state; - } -}; diff --git a/app/renderer/src/store/timer/types.ts b/app/renderer/src/store/timer/types.ts index 067f7d4a..11a205bc 100644 --- a/app/renderer/src/store/timer/types.ts +++ b/app/renderer/src/store/timer/types.ts @@ -1,55 +1,18 @@ -const timer = "[timer]"; +import type { PayloadAction } from "@reduxjs/toolkit"; export type TimerTypes = { round: number; playing: boolean; - timerType: - | "STAY_FOCUS" - | "SHORT_BREAK" - | "LONG_BREAK" - | "SPECIAL_BREAK"; + timerType: TimerStatus; }; -export const SET_PLAY = `${timer} SET_PLAY`; -export const SKIP_TIMER = `${timer} SKIP_TIMER`; -export const RESTART_TIMER = `${timer} RESTART_TIMER`; - -export const SET_TIMER_TYPE = `${timer} SET_TIMER_TYPE`; -export const SET_ROUND = `${timer} SET_ROUND`; - -export const STAY_FOCUS = "STAY_FOCUS"; -export const SHORT_BREAK = "SHORT_BREAK"; -export const LONG_BREAK = "LONG_BREAK"; -export const SPECIAL_BREAK = "SPECIAL_BREAK"; - -interface SetPlay { - type: typeof SET_PLAY; - payload?: TimerTypes["playing"]; -} - -interface SkipTimer { - type: typeof SKIP_TIMER; - payload?: any; -} - -interface RestartTimer { - type: typeof RESTART_TIMER; - payload?: any; -} - -interface SetTimerType { - type: typeof SET_TIMER_TYPE; - payload?: TimerTypes["timerType"]; -} - -interface SetRound { - type: typeof SET_ROUND; - payload?: TimerTypes["round"]; +export const enum TimerStatus { + STAY_FOCUS = "STAY_FOCUS", + SHORT_BREAK = "SHORT_BREAK", + LONG_BREAK = "LONG_BREAK", + SPECIAL_BREAK = "SPECIAL_BREAK", } -export type TimerActionTypes = - | SetPlay - | SkipTimer - | RestartTimer - | SetTimerType - | SetRound; +export type TimerPayload = PayloadAction< + TimerTypes[T] +>; diff --git a/app/renderer/src/store/update/actions.ts b/app/renderer/src/store/update/actions.ts deleted file mode 100644 index 0eaeb36c..00000000 --- a/app/renderer/src/store/update/actions.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - UpdateTypes, - UpdateActionTypes, - UPDATE_BODY, - UPDATE_VERSION, -} from "./types"; - -export const setUpdateBody = ( - updateBody: UpdateTypes["updateBody"] -): UpdateActionTypes => { - return { - type: UPDATE_BODY, - payload: updateBody, - }; -}; - -export const setUpdateVersion = ( - updateVersion: UpdateTypes["updateVersion"] -): UpdateActionTypes => { - return { - type: UPDATE_VERSION, - payload: updateVersion, - }; -}; diff --git a/app/renderer/src/store/update/index.ts b/app/renderer/src/store/update/index.ts index 6d30c416..18aae3ff 100644 --- a/app/renderer/src/store/update/index.ts +++ b/app/renderer/src/store/update/index.ts @@ -1,3 +1,40 @@ -export * from "./actions"; -export * from "./reducer"; -export * from "./types"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { getFromStorage } from "utils"; + +export type UpdateTypes = { + updateVersion: string; + updateBody: string | undefined; +}; + +type UploadPayload = PayloadAction< + UpdateTypes[T] +>; + +const defaultUpdateStatus: Readonly = Object.freeze({ + updateBody: undefined, + updateVersion: "", +}); + +const updateStatus = + (getFromStorage("state") && getFromStorage("state").update) || + defaultUpdateStatus; + +const initialState: UpdateTypes = updateStatus; + +const updateSlice = createSlice({ + name: "update", + initialState, + reducers: { + setUpdateBody(state, action: UploadPayload<"updateBody">) { + state.updateBody = action.payload; + }, + + setUpdateVersion(state, action: UploadPayload<"updateVersion">) { + state.updateVersion = action.payload; + }, + }, +}); + +export const { setUpdateBody, setUpdateVersion } = updateSlice.actions; + +export default updateSlice.reducer; diff --git a/app/renderer/src/store/update/reducer.ts b/app/renderer/src/store/update/reducer.ts deleted file mode 100644 index b762586e..00000000 --- a/app/renderer/src/store/update/reducer.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getFromStorage, isPreferredDark, detectOS } from "utils"; -import { - UpdateTypes, - UpdateActionTypes, - UPDATE_BODY, - UPDATE_VERSION, -} from "./types"; - -const defaultSettings: UpdateTypes = { - updateBody: undefined, - updateVersion: "", -}; - -const settings = - (getFromStorage("state") && getFromStorage("state").settings) || - defaultSettings; - -const initialState: UpdateTypes = settings; - -export const updateReducer = ( - state = initialState, - action: UpdateActionTypes -) => { - switch (action.type) { - case UPDATE_BODY: - return { - ...state, - updateBody: action.payload, - }; - case UPDATE_VERSION: - return { - ...state, - updateVersion: action.payload, - }; - default: - return state; - } -}; diff --git a/app/renderer/src/store/update/types.ts b/app/renderer/src/store/update/types.ts deleted file mode 100644 index 214c0232..00000000 --- a/app/renderer/src/store/update/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -const update = "[update]"; - -export type UpdateTypes = { - updateVersion: string; - updateBody: string | undefined; -}; -export const UPDATE_BODY = `${update} UPDATE_BODY`; -export const UPDATE_VERSION = `${update} UPDATE_VERSION`; - -export type UpdateActionTypes = { - type: string; - payload: any; -}; diff --git a/app/renderer/src/styles/components/navigation.ts b/app/renderer/src/styles/components/navigation.ts index 242b11dd..9f18e948 100644 --- a/app/renderer/src/styles/components/navigation.ts +++ b/app/renderer/src/styles/components/navigation.ts @@ -1,5 +1,5 @@ import styled from "styled-components/macro"; -import { SHORT_BREAK, LONG_BREAK, SPECIAL_BREAK } from "store"; +import { TimerStatus } from "store/timer/types"; import { NavLink } from "react-router-dom"; import { themes } from "../themes"; @@ -46,7 +46,7 @@ export const StyledNavListItem = styled.li` align-items: center; `; -type NavLinkProps = { type?: string }; +type NavLinkProps = { type?: TimerStatus }; export const StyledNavIconWrapper = styled.div` position: relative; @@ -79,13 +79,13 @@ export const StyledNavLink = styled(NavLink)` &.active { color: ${(p) => - (p.type === SHORT_BREAK && + (p.type === TimerStatus.SHORT_BREAK && p.to === "/" && "var(--color-green)") || - (p.type === LONG_BREAK && + (p.type === TimerStatus.LONG_BREAK && p.to === "/" && "var(--color-yellow)") || - (p.type === SPECIAL_BREAK && + (p.type === TimerStatus.SPECIAL_BREAK && p.to === "/" && "var(--color-yellow)") || "var(--color-primary)"}; diff --git a/app/renderer/src/styles/routes/timer/control.ts b/app/renderer/src/styles/routes/timer/control.ts index 2f3e169a..9265a393 100644 --- a/app/renderer/src/styles/routes/timer/control.ts +++ b/app/renderer/src/styles/routes/timer/control.ts @@ -1,10 +1,5 @@ import styled, { css } from "styled-components/macro"; -import { - SHORT_BREAK, - LONG_BREAK, - SPECIAL_BREAK, - TimerTypes, -} from "store"; +import { TimerStatus } from "store/timer/types"; import { themes } from "../../themes"; const ControlButton = css` @@ -34,7 +29,7 @@ const ControlButton = css` } `; -type ControlProps = { type?: TimerTypes["timerType"] }; +type ControlProps = { type?: TimerStatus }; export const StyledControlSpacer = styled.div` flex: 1 0; @@ -53,16 +48,19 @@ export const StyledControl = styled.div` column-gap: 1rem; color: ${(p) => - (p.type === SHORT_BREAK && "var(--color-green)") || - (p.type === LONG_BREAK && "var(--color-yellow)") || - (p.type === SPECIAL_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SHORT_BREAK && "var(--color-green)") || + (p.type === TimerStatus.LONG_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SPECIAL_BREAK && "var(--color-yellow)") || "var(--color-primary)"}; .ripple-hook { background-color: ${(p) => - (p.type === SHORT_BREAK && "var(--color-bg-ripple-green)") || - (p.type === LONG_BREAK && "var(--color-bg-ripple-yellow)") || - (p.type === SPECIAL_BREAK && "var(--color-bg-ripple-yellow)") || + (p.type === TimerStatus.SHORT_BREAK && + "var(--color-bg-ripple-green)") || + (p.type === TimerStatus.LONG_BREAK && + "var(--color-bg-ripple-yellow)") || + (p.type === TimerStatus.SPECIAL_BREAK && + "var(--color-bg-ripple-yellow)") || "var(--color-bg-ripple-primary)"}; } @@ -72,7 +70,7 @@ export const StyledControl = styled.div` } `; -type SessionProps = { timerType?: TimerTypes["timerType"] }; +type SessionProps = { timerType?: TimerStatus }; export const StyledSessionReset = styled.button` position: absolute; @@ -100,9 +98,12 @@ export const StyledSessionReset = styled.button` &:hover { color: ${(p) => - (p.timerType === SHORT_BREAK && "var(--color-green)") || - (p.timerType === LONG_BREAK && "var(--color-yellow)") || - (p.timerType === SPECIAL_BREAK && "var(--color-yellow)") || + (p.timerType === TimerStatus.SHORT_BREAK && + "var(--color-green)") || + (p.timerType === TimerStatus.LONG_BREAK && + "var(--color-yellow)") || + (p.timerType === TimerStatus.SPECIAL_BREAK && + "var(--color-yellow)") || "var(--color-primary)"}; } diff --git a/app/renderer/src/styles/routes/timer/counter.ts b/app/renderer/src/styles/routes/timer/counter.ts index a47a7411..106c45bb 100644 --- a/app/renderer/src/styles/routes/timer/counter.ts +++ b/app/renderer/src/styles/routes/timer/counter.ts @@ -1,18 +1,19 @@ import styled, { css } from "styled-components/macro"; -import { LONG_BREAK, SHORT_BREAK, SPECIAL_BREAK } from "store"; +import { TimerStatus } from "store/timer/types"; import { ProgressSVG } from "assets/icons"; export type ProgressProps = { offset: number; animate: "true" | "false"; + type?: TimerStatus; }; export const StyledCounterProgress = styled(ProgressSVG)` #progress { stroke: ${(p) => - (p.type === SHORT_BREAK && "var(--color-green)") || - (p.type === LONG_BREAK && "var(--color-yellow)") || - (p.type === SPECIAL_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SHORT_BREAK && "var(--color-green)") || + (p.type === TimerStatus.LONG_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SPECIAL_BREAK && "var(--color-yellow)") || "var(--color-primary)"}; stroke-width: 0.6rem; stroke-linecap: round; @@ -115,7 +116,7 @@ export const StyledCounterType = styled.div` `; type TimerProps = { - type?: string; + type?: TimerStatus; hours: string; } & CounterContainerProps; @@ -123,9 +124,9 @@ export const StyledCounterTimer = styled.h3` font-size: 4rem; font-weight: 400; color: ${(p) => - (p.type === SHORT_BREAK && "var(--color-green)") || - (p.type === LONG_BREAK && "var(--color-yellow)") || - (p.type === SPECIAL_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SHORT_BREAK && "var(--color-green)") || + (p.type === TimerStatus.LONG_BREAK && "var(--color-yellow)") || + (p.type === TimerStatus.SPECIAL_BREAK && "var(--color-yellow)") || "var(--color-primary)"}; line-height: 1.2; diff --git a/yarn.lock b/yarn.lock index 6e4f99ad..da65d750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3412,6 +3412,16 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@reduxjs/toolkit@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.0.1.tgz#0a5233c1e35c1941b03aece39cceade3467a1062" + integrity sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA== + dependencies: + immer "^10.0.3" + redux "^5.0.0" + redux-thunk "^3.1.0" + reselect "^5.0.1" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -9901,6 +9911,11 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== +immer@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.3.tgz#a8de42065e964aa3edf6afc282dfc7f7f34ae3c9" + integrity sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A== + immer@^9.0.7: version "9.0.21" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" @@ -15245,10 +15260,10 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" -redux-devtools-extension@^2.13.8: - version "2.13.9" - resolved "https://registry.yarnpkg.com/redux-devtools-extension/-/redux-devtools-extension-2.13.9.tgz#6b764e8028b507adcb75a1cae790f71e6be08ae7" - integrity sha512-cNJ8Q/EtjhQaZ71c8I9+BPySIBVEKssbPpskBfsXqb8HJ002A3KRVHfeRzwRo6mGPqsm7XuHTqNSNeS1Khig0A== +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== redux-undo@^1.0.1: version "1.0.1" @@ -15262,6 +15277,11 @@ redux@^4.0.0, redux@^4.0.4, redux@^4.0.5: dependencies: "@babel/runtime" "^7.9.2" +redux@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + reflect.getprototypeof@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" @@ -15489,6 +15509,11 @@ requires-port@^1.0.0: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== +reselect@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.0.tgz#c479139ab9dd91be4d9c764a7f3868210ef8cd21" + integrity sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"