diff --git a/package-lock.json b/package-lock.json index 453e6654d..439914eb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "react-hook-form": "^7.52.2", "react-i18next": "^15.0.1", "react-icons": "^5.3.0", + "react-transition-group": "^4.4.5", "uuid": "^10.0.0", "vite-tsconfig-paths": "^5.0.1", "zustand": "^4.5.5" diff --git a/package.json b/package.json index be07ee5c6..64ab30ee3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "react-i18next": "^15.0.1", "react-icons": "^5.3.0", "uuid": "^10.0.0", + "react-transition-group": "^4.4.5", "vite-tsconfig-paths": "^5.0.1", "zustand": "^4.5.5" }, diff --git a/public/locales/en/common.json b/public/locales/en/common.json index f5fd008e8..1b91a182f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -12,5 +12,6 @@ "resources": "Resources", "hashrate": "Hashrate", "utilization": "Utilization", - "mode": "Mode" + "mode": "Mode", + "day": "Day" } \ No newline at end of file diff --git a/public/locales/en/mining-view.json b/public/locales/en/mining-view.json index 611cf16d5..c90d3d3f9 100644 --- a/public/locales/en/mining-view.json +++ b/public/locales/en/mining-view.json @@ -7,6 +7,7 @@ "starting-mining": "Starting Mining", "start-mining": "Start Mining", "pause-mining": "Pause Mining", + "changing-mode": "Changing mode", "cancel-mining": "Cancel Mining", "waiting-for-idle": "Waiting for Idle", "started-auto-mining": "Auto Mining Started" diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index 12d8c0342..60112e29b 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -12,5 +12,6 @@ "resources": "Zasoby", "hashrate": "Prędkość ( hashrate )", "utilization": "Wykorzystanie", - "mode": "Tryb" + "mode": "Tryb", + "day": "Dzień" } \ No newline at end of file diff --git a/public/locales/pl/mining-view.json b/public/locales/pl/mining-view.json index 782895c27..48e940d29 100644 --- a/public/locales/pl/mining-view.json +++ b/public/locales/pl/mining-view.json @@ -7,6 +7,7 @@ "starting-mining": "Uruchamianie kopania", "start-mining": "Uruchom kopanie", "pause-mining": "Wstrzymaj kopanie", + "changing-mode": "Zmiana trybu", "cancel-mining": "Anuluj kopanie", "waiting-for-idle": "Oczekiwanie na bezczynność", "started-auto-mining": "Automatyczne kopanie uruchomione" diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 4d2ed21e4..75902fd10 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -62,21 +62,9 @@ mod setup_status_event; #[tauri::command] async fn set_mode<'r>( mode: String, - window: tauri::Window, state: tauri::State<'r, UniverseAppState>, - app: tauri::AppHandle, ) -> Result<(), String> { - match stop_mining(state.clone()).await { - Ok(_) => { - let _ = state.config.write().await.set_mode(mode).await; - match start_mining(window, state.clone(), app).await { - Ok(_) => {} - Err(e) => warn!(target: LOG_TARGET, "Failed to start mining: {}", e.to_string()), - }; - } - Err(e) => warn!(target: LOG_TARGET, "Failed to stop mining: {}", e.to_string()), - }; - + let _ = state.config.write().await.set_mode(mode).await; Ok(()) } diff --git a/src/App.tsx b/src/App.tsx index 3dcc1d69a..880b0e9b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,12 +14,14 @@ import { useSetUp } from './hooks/useSetUp.ts'; import { useEnvironment } from './hooks/useEnvironment.ts'; import { useAirdropTokensRefresh } from './hooks/airdrop/useAirdropTokensRefresh.ts'; import { SplashScreen } from './containers/SplashScreen'; +import { useMiningEffects } from './hooks/mining/useMiningEffects.ts'; function App() { useAirdropTokensRefresh(); useSetUp(); useGetStatus(); useEnvironment(); + useMiningEffects(); const view = useUIStore((s) => s.view); const showSplash = useUIStore((s) => s.showSplash); diff --git a/src/containers/SideBar/Miner/Miner.tsx b/src/containers/SideBar/Miner/Miner.tsx index ff9759a0a..42ec3b76b 100644 --- a/src/containers/SideBar/Miner/Miner.tsx +++ b/src/containers/SideBar/Miner/Miner.tsx @@ -4,18 +4,20 @@ import AutoMiner from './components/AutoMiner/AutoMiner.tsx'; import ModeSelect from './components/ModeSelect.tsx'; import { useHardwareStatus } from '../../../hooks/useHardwareStatus.ts'; -import { Divider } from '@mui/material'; +import { Box, Divider, Fade, Slide, Stack } from '@mui/material'; import { useCPUStatusStore } from '@app/store/useCPUStatusStore.ts'; import { useMiningControls } from '@app/hooks/mining/useMiningControls.ts'; import { formatNumber } from '@app/utils/formatNumber.ts'; +import { useRef } from 'react'; +import { TransitionGroup } from 'react-transition-group'; import { useTranslation } from 'react-i18next'; function Miner() { const { t } = useTranslation('common', { useSuspense: false }); const { cpu: cpuHardwareStatus } = useHardwareStatus(); - const { isWaitingForHashRate } = useMiningControls(); + const { isWaitingForHashRate, isMiningEnabled, isChangingMode } = useMiningControls(); const hash_rate = useCPUStatusStore((s) => s.hash_rate); const estimated_earnings = useCPUStatusStore((s) => s.estimated_earnings); @@ -31,33 +33,55 @@ function Miner() { }) .replace(/,/g, '.'); + const containerRef = useRef(null); + return ( - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/src/containers/SideBar/Miner/components/ModeSelect.tsx b/src/containers/SideBar/Miner/components/ModeSelect.tsx index 1641d645e..d4179a87f 100644 --- a/src/containers/SideBar/Miner/components/ModeSelect.tsx +++ b/src/containers/SideBar/Miner/components/ModeSelect.tsx @@ -8,6 +8,8 @@ import { Typography } from '@mui/material'; import { TileItem } from '../styles'; import { useAppStatusStore } from '@app/store/useAppStatusStore.ts'; import { Theme, useTheme } from '@mui/material/styles'; +import { useMiningControls } from '@app/hooks/mining/useMiningControls'; +import { StyledIcon } from '@app/containers/Dashboard/MiningView/components/MiningButton.styles'; import { useTranslation } from 'react-i18next'; const CustomSelect = styled(Select)(({ theme }: { theme: Theme }) => ({ @@ -27,10 +29,10 @@ function ModeSelect() { const { t } = useTranslation('common', { useSuspense: false }); const mode = useAppStatusStore((s) => s.mode); - const setConfigMode = useAppStatusStore((s) => s.setConfigMode); + const { changeMode, isChangingMode } = useMiningControls(); const handleChange = (event: SelectChangeEvent) => { - setConfigMode(event.target.value as modeType); + changeMode(event.target.value as modeType); }; const theme = useTheme(); return ( @@ -43,7 +45,8 @@ function ModeSelect() { theme={theme} value={mode} onChange={handleChange} - IconComponent={IoCode} + disabled={isChangingMode} + IconComponent={isChangingMode ? StyledIcon : IoCode} sx={{ '& .MuiSelect-icon': { transform: 'rotate(90deg)', diff --git a/src/containers/SideBar/Miner/components/Tile.tsx b/src/containers/SideBar/Miner/components/Tile.tsx index bf1c332a5..d404c3e13 100644 --- a/src/containers/SideBar/Miner/components/Tile.tsx +++ b/src/containers/SideBar/Miner/components/Tile.tsx @@ -14,7 +14,7 @@ function Tile({ title, stats, isLoading }: TileProps) { {title} {isLoading ? ( - + ) : ( {truncateString(stats, 10)} diff --git a/src/containers/SideBar/Miner/styles.ts b/src/containers/SideBar/Miner/styles.ts index 706a8ba39..044b4bcee 100644 --- a/src/containers/SideBar/Miner/styles.ts +++ b/src/containers/SideBar/Miner/styles.ts @@ -21,6 +21,7 @@ export const TileItem = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, borderRadius: theme.shape.borderRadius, boxShadow: '0px 4px 45px 0px rgba(0, 0, 0, 0.08)', + maxWidth: '152px', })); export const ScheduleButton = styled(Button)(({ theme }) => ({ diff --git a/src/hooks/mining/useMiningControls.ts b/src/hooks/mining/useMiningControls.ts index 00e03485f..10ec502a9 100644 --- a/src/hooks/mining/useMiningControls.ts +++ b/src/hooks/mining/useMiningControls.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useMemo } from 'react'; import { invoke } from '@tauri-apps/api/tauri'; import { useVisualisation } from './useVisualisation.ts'; @@ -11,6 +11,7 @@ export enum MiningButtonStateText { STARTING = 'starting-mining', STARTED = 'pause-mining', CONNECTION_LOST = 'cancel-mining', + CHANGING_MODE = 'changing-mode', START = 'start-mining', AUTO_MINING = 'waiting-for-idle', AUTO_MINING_STARTED = 'started-auto-mining', @@ -26,17 +27,25 @@ export function useMiningControls() { isMiningEnabled: s.isMiningEnabled, setIsMiningEnabled: s.setIsMiningEnabled, })); - const { isConnectionLostDuringMining, setIsConnectionLostDuringMining } = useUIStore((s) => ({ + const { isConnectionLostDuringMining } = useUIStore((s) => ({ isConnectionLostDuringMining: s.isConnectionLostDuringMining, setIsConnectionLostDuringMining: s.setIsConnectionLostDuringMining, })); - const isMiningInProgress = useRef(false); + const { isChangingMode, setIsChangingMode } = useUIStore((s) => ({ + isChangingMode: s.isChangingMode, + setIsChangingMode: s.setIsChangingMode, + })); + const { isMiningInProgress } = useUIStore((s) => ({ + isMiningInProgress: s.isMiningInProgress, + setIsMiningInProgress: s.setIsMiningInProgress, + })); const isLoading = useMemo(() => { if (isConnectionLostDuringMining) return false; + if (isChangingMode) return true; return !isMining && isMiningEnabled; - }, [isMining, isMiningEnabled, isConnectionLostDuringMining]); + }, [isMining, isMiningEnabled, isConnectionLostDuringMining, isChangingMode]); const isWaitingForHashRate = useMemo(() => { return isLoading || (isMining && hashRate <= 0); @@ -45,28 +54,29 @@ export function useMiningControls() { const shouldMiningControlsBeEnabled = useMemo(() => { if (isConnectionLostDuringMining) return true; + if (isChangingMode) return false; + if (!isMining && isMiningEnabled) return false; if (isMining && progress < 1) return true; return progress >= 1 && !isAutoMining; - }, [isAutoMining, isMining, progress, isMiningEnabled, isConnectionLostDuringMining]); + }, [isAutoMining, isMining, progress, isMiningEnabled, isConnectionLostDuringMining, isChangingMode]); const shouldAutoMiningControlsBeEnabled = useMemo(() => { if (isMiningEnabled && !isAutoMining) return false; + + if (isChangingMode) return false; + if (isMining && progress < 1) return true; return progress >= 1; - }, [isAutoMining, isMining, progress, isMiningEnabled]); + }, [isAutoMining, isMining, progress, isMiningEnabled, isChangingMode]); const startMining = useCallback(async () => { setIsMiningEnabled(true); - await invoke('start_mining', {}) - .then(() => { - console.info(`mining started`); - }) - .catch(() => { - setIsMiningEnabled(false); - }); + await invoke('start_mining', {}).catch(() => { + setIsMiningEnabled(false); + }); }, [setIsMiningEnabled]); const stopMining = useCallback(async () => { @@ -90,31 +100,52 @@ export function useMiningControls() { }); }, [handleVisual, setIsMiningEnabled]); - useEffect(() => { - if (isMining && isMiningEnabled) { - if (isConnectionLostDuringMining) setIsConnectionLostDuringMining(false); - console.info('useEffect: handleVisual start'); - handleVisual('start').then(() => { - isMiningInProgress.current = true; - }); - } - - if (!isMining && !isMiningEnabled) { - if (isConnectionLostDuringMining) setIsConnectionLostDuringMining(false); - console.info('useEffect: handleVisual stop'); - handleVisual('stop').then(() => { - isMiningInProgress.current = false; - }); - } + const changeMode = useCallback( + async (mode: string) => { + const hasBeenMining = isMiningInProgress; + + if (!hasBeenMining || isAutoMining) { + await invoke('set_mode', { mode }); + return; + } + + setIsChangingMode(true); + if (hasBeenMining && !isConnectionLostDuringMining) { + await stopMining(); + } + + if (isConnectionLostDuringMining) { + await cancelMining(); + } + + await invoke('set_mode', { mode }); + + if (hasBeenMining && !isConnectionLostDuringMining) { + setTimeout(async () => { + await startMining(); + }, 2000); + } + + if (isConnectionLostDuringMining) { + setIsChangingMode(false); + } + }, + [ + isMiningInProgress, + isConnectionLostDuringMining, + isAutoMining, + cancelMining, + setIsChangingMode, + startMining, + stopMining, + ] + ); - if (!isMining && isMiningInProgress.current) { - console.info('useEffect: handleVisual pause'); - setIsConnectionLostDuringMining(true); - void handleVisual('pause'); + const getMiningButtonStateText = useCallback(() => { + if (isChangingMode) { + return MiningButtonStateText.CHANGING_MODE; } - }, [handleVisual, isMining, isMiningEnabled, isConnectionLostDuringMining, setIsConnectionLostDuringMining]); - const getMiningButtonStateText = useCallback(() => { if (isConnectionLostDuringMining) { return MiningButtonStateText.CONNECTION_LOST; } @@ -136,10 +167,12 @@ export function useMiningControls() { } return MiningButtonStateText.START; - }, [isAutoMining, isMining, isMiningEnabled, isConnectionLostDuringMining]); + }, [isAutoMining, isMining, isMiningEnabled, isConnectionLostDuringMining, isChangingMode]); return { + isMiningEnabled, cancelMining, + changeMode, isConnectionLostDuringMining, isLoading, startMining, @@ -148,5 +181,6 @@ export function useMiningControls() { isWaitingForHashRate, shouldMiningControlsBeEnabled, shouldAutoMiningControlsBeEnabled, + isChangingMode, }; } diff --git a/src/hooks/mining/useMiningEffects.ts b/src/hooks/mining/useMiningEffects.ts new file mode 100644 index 000000000..38a6e9769 --- /dev/null +++ b/src/hooks/mining/useMiningEffects.ts @@ -0,0 +1,62 @@ +import { useCPUStatusStore } from '@app/store/useCPUStatusStore'; +import { useUIStore } from '@app/store/useUIStore'; +import { useEffect } from 'react'; +import { useVisualisation } from './useVisualisation'; + +export const useMiningEffects = () => { + const isMining = useCPUStatusStore((s) => s.is_mining); + const handleVisual = useVisualisation(); + + const { isConnectionLostDuringMining, setIsConnectionLostDuringMining } = useUIStore((s) => ({ + isConnectionLostDuringMining: s.isConnectionLostDuringMining, + setIsConnectionLostDuringMining: s.setIsConnectionLostDuringMining, + })); + + const { isMiningInProgress, setIsMiningInProgress } = useUIStore((s) => ({ + isMiningInProgress: s.isMiningInProgress, + setIsMiningInProgress: s.setIsMiningInProgress, + })); + + const { isMiningEnabled } = useUIStore((s) => ({ + isMiningEnabled: s.isMiningEnabled, + })); + + const { isChangingMode, setIsChangingMode } = useUIStore((s) => ({ + isChangingMode: s.isChangingMode, + setIsChangingMode: s.setIsChangingMode, + })); + + // We probably should remove it in the future and relay on events emited from rust code ? + useEffect(() => { + if (isMining && isMiningEnabled) { + if (isConnectionLostDuringMining) setIsConnectionLostDuringMining(false); + if (isChangingMode) setIsChangingMode(false); + handleVisual('start'); + setIsMiningInProgress(true); + return; + } + + if (!isMining && !isMiningEnabled && isMiningInProgress) { + if (isConnectionLostDuringMining) setIsConnectionLostDuringMining(false); + handleVisual('stop'); + setIsMiningInProgress(false); + return; + } + + if (!isMining && isMiningInProgress && !isChangingMode) { + setIsConnectionLostDuringMining(true); + handleVisual('pause'); + return; + } + }, [ + handleVisual, + isMining, + isMiningEnabled, + isConnectionLostDuringMining, + isChangingMode, + isMiningInProgress, + setIsChangingMode, + setIsConnectionLostDuringMining, + setIsMiningInProgress, + ]); +}; diff --git a/src/store/useAppStatusStore.ts b/src/store/useAppStatusStore.ts index 86288188d..f7537b254 100644 --- a/src/store/useAppStatusStore.ts +++ b/src/store/useAppStatusStore.ts @@ -2,14 +2,12 @@ import { create } from 'zustand'; import { ApplicationsVersions, AppStatus } from '../types/app-status.ts'; import { modeType } from './types.ts'; import { persist } from 'zustand/middleware'; -import { invoke } from '@tauri-apps/api/tauri'; type State = Partial; interface Actions { setAppStatus: (appStatus: AppStatus) => void; setApplicationsVersions: (applicationsVersions: ApplicationsVersions) => void; setMode: (mode: modeType) => void; - setConfigMode: (mode: modeType) => void; setCurrentUserInactivityDuration: (duration: number) => void; } type AppStatusStoreState = State & Actions; @@ -34,15 +32,6 @@ export const useAppStatusStore = create()( set({ current_user_inactivity_duration }), setApplicationsVersions: (applications_versions) => set({ applications_versions }), setMode: (mode) => set({ mode }), - setConfigMode: async (mode) => { - try { - await invoke('set_mode', { mode }); - set({ mode }); - console.info(`Mode changed to ${mode}`); - } catch (e) { - console.error('Could not change the mode', e); - } - }, }), { name: 'status-store', diff --git a/src/store/useUIStore.ts b/src/store/useUIStore.ts index c7e70267b..bab28080a 100644 --- a/src/store/useUIStore.ts +++ b/src/store/useUIStore.ts @@ -11,6 +11,8 @@ interface State { sidebarOpen: boolean; isMiningSwitchingState: boolean; isMiningEnabled: boolean; + isMiningInProgress: boolean; + isChangingMode: boolean; isConnectionLostDuringMining: boolean; } interface Actions { @@ -23,6 +25,8 @@ interface Actions { setIsMiningSwitchingState: (isMiningSwitchingState: State['isMiningSwitchingState']) => void; setIsMiningEnabled: (isMiningEnabled: State['isMiningEnabled']) => void; setIsConnectionLostDuringMining: (isConnectionLostDuringMining: State['isConnectionLostDuringMining']) => void; + setIsMiningInProgress: (isMiningInProgress: State['isMiningInProgress']) => void; + setIsChangingMode: (isChangingMode: State['isChangingMode']) => void; } type UIStoreState = State & Actions; @@ -36,6 +40,8 @@ const initialState: State = { sidebarOpen: false, isMiningSwitchingState: false, isMiningEnabled: false, + isMiningInProgress: false, + isChangingMode: false, isConnectionLostDuringMining: false, }; @@ -52,6 +58,8 @@ export const useUIStore = create()( setIsMiningSwitchingState: (isMiningSwitchingState) => set({ isMiningSwitchingState }), setIsMiningEnabled: (isMiningEnabled) => set({ isMiningEnabled }), setIsConnectionLostDuringMining: (isConnectionLostDuringMining) => set({ isConnectionLostDuringMining }), + setIsMiningInProgress: (isMiningInProgress) => set({ isMiningInProgress }), + setIsChangingMode: (isChangingMode) => set({ isChangingMode }), }), { name: 'ui-store',