From 7e5e99c5f86cbaf31cf116cfdaa9ba46e44d13fe Mon Sep 17 00:00:00 2001 From: Juan <47256233+PanchoBubble@users.noreply.github.com> Date: Wed, 6 Nov 2024 13:09:33 +0000 Subject: [PATCH] fix: power levels adjustments (#1007) We need to: Fix the overlapping marker and labels Change slider to % increments but say x of y cores or x of y threads on slider description so that it communicates both a % progress and the core/thread increments Revise the save state to be explicit, since it takes about 30 seconds to apply It currently defaults to 1 irrespective of whether Eco or Ludicrous is set, which is confusing: it should default to whatever the user had selected (the Eco level or the Ludicrous level) ________________ https://www.loom.com/share/9e7dc4c1123c4701b8032c55a2861f83 Fixes: #1016 --------- Co-authored-by: Brian Pearce --- public/locales/en/settings.json | 8 +- src-tauri/src/main.rs | 6 +- .../CustomPowerLevelsDialog.styles.ts | 44 +++- .../components/CustomPowerLevelsDialog.tsx | 244 ++++++++++++------ 4 files changed, 198 insertions(+), 104 deletions(-) diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index 7b69cb923..2b23dd6aa 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -12,11 +12,11 @@ "custom-power-levels": { "title": "Custom Mode", "warning": "Warning", - "saved": "Changes saved!", + "saved": "Applying Changes", "gpu-power-level": "GPU Power %", "cpu-power-level": "CPU Cores", - "choose-gpu-power-level": "Choose how many threads you want your GPU to allocat to mining", - "choose-cpu-power-level": "Choose how many cores from your CPU to be allocated to mining", + "choose-gpu-power-level": "Choose how much you want your GPU to be allocated to mining ({{current}} out of {{max}} threads)", + "choose-cpu-power-level": "Choose how much you want your CPU to be allocated to mining ({{current}} out of {{max}} cores)", "cpu-warning": "High CPU usage can cause overheating, increase energy consumption, and may lead to hardware damage over time. Please monitor usage to ensure safe and efficient operation.", "gpu-warning": "High GPU usage can cause overheating, increase energy consumption, and may lead to hardware damage over time. Please monitor usage to ensure safe and efficient operation." }, @@ -127,4 +127,4 @@ "yes": "Yes", "your-feedback": "Describe your issue, including your Telegram handle if you have one, so that we can contact you with updates.", "your-reference": "Your reference:
{{logRef}}" -} \ No newline at end of file +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 453800493..523500706 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -173,13 +173,13 @@ async fn set_mode( let timer = Instant::now(); info!(target: LOG_TARGET, "set_mode called with mode: {:?}, custom_max_cpu_usage: {:?}, custom_max_gpu_usage: {:?}", mode, custom_cpu_usage, custom_gpu_usage); - fn f64_to_isize_safe(_value: Option) -> Option { - let value = _value.unwrap_or(-1.0); + fn f64_to_isize_safe(value: Option) -> Option { + let value = value.unwrap_or_default(); if value.is_finite() && value >= isize::MIN as f64 && value <= isize::MAX as f64 { #[allow(clippy::cast_possible_truncation)] Some(value.round() as isize) } else { - warn!(target: LOG_TARGET, "Invalid value for custom_cpu_usage or custom_gpu_usage: {:?}", _value); + warn!(target: LOG_TARGET, "Invalid value for custom_cpu_usage or custom_gpu_usage: {:?}", value); None } } diff --git a/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.styles.ts b/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.styles.ts index 682c65288..bc193c09b 100644 --- a/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.styles.ts +++ b/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.styles.ts @@ -1,5 +1,8 @@ import styled, { css } from 'styled-components'; +export const SLIDER_WIDTH = 570; +export const SLIDER_THUMB_WIDTH = 30; + export const CustomLevelsContent = styled.div` padding: 15px 15px 35px; margin-top: 10px; @@ -17,20 +20,21 @@ export const RangeInput = styled.input` width: 100%; height: 9px; border-radius: 5px; - background: #813bf5; + background: #ddd; outline: none; -webkit-transition: 0.2s; transition: opacity 0.2s; - width: 600px; + width: ${SLIDER_WIDTH}px; &::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; - width: 30px; - height: 30px; + width: ${SLIDER_THUMB_WIDTH}px; + height: ${SLIDER_THUMB_WIDTH}px; border-radius: 50%; background: white; border: 2px solid #813bf5; + z-index: 10; cursor: pointer; transition: background 0.15s ease-in-out; } @@ -53,6 +57,7 @@ export const RangeLabel = styled.label` align-items: center; font-size: 14px; font-family: Poppins, sans-serif; + padding-bottom: 10px; `; export const InputDescription = styled.div` @@ -62,6 +67,10 @@ export const InputDescription = styled.div` font-size: 12px; font-family: Poppins, sans-serif; color: #6c6d8a; + padding-bottom: 10px; + span { + font-weight: bold; + } `; export const RangeIntputWrapper = styled.div` @@ -97,7 +106,7 @@ export const RangeValueHolder = styled.div` color: #fff; justify-content: center; align-items: center; - transform: translateX(-50%); + transform: translateX(-50%) translateZ(0); background: #813bf5; position: absolute; min-width: 20px; @@ -119,14 +128,23 @@ export const RangeValueHolder = styled.div` } `; -export const IconImage = styled.img<{ $left: number }>` - width: 20px; - height: 20px; +export const PerformanceMarker = styled.div<{ $red?: boolean }>` position: absolute; - top: -20px; - left: ${({ $left }) => ` - ${$left}% - `}; + transform: translateX(-50%); + width: 5px; + height: 5px; + border-radius: 50%; + + z-index: 3; + + top: 8px; + + background: #62cc32; + ${({ $red }) => + $red && + css` + background: #ff0000; + `} `; export const WarningContainer = styled.div<{ $visible: boolean }>` @@ -145,7 +163,7 @@ export const WarningContainer = styled.div<{ $visible: boolean }>` ${({ $visible }) => $visible && css` - opacity: 1; + opacity: 0.7; padding: 8px 15px; height: 50px; `} diff --git a/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.tsx b/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.tsx index c95a7b505..81e94c905 100644 --- a/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.tsx +++ b/src/containers/SideBar/Miner/components/CustomPowerLevelsDialog.tsx @@ -1,6 +1,6 @@ import { Dialog, DialogContent } from '@app/components/elements/dialog/Dialog.tsx'; import { useMiningStore } from '@app/store/useMiningStore'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { MaxConsumptionLevels } from '@app/types/app-status'; import { invoke } from '@tauri-apps/api/tauri'; import { useAppConfigStore } from '@app/store/useAppConfigStore'; @@ -13,87 +13,58 @@ import { RangeLabel, RangeLimits, InputContainer, - IconImage, + PerformanceMarker, RangeValueHolder, RangeInputHolder, WarningContainer, SuccessContainer, TopRightContainer, + SLIDER_WIDTH, + SLIDER_THUMB_WIDTH, } from './CustomPowerLevelsDialog.styles.ts'; import { useTranslation } from 'react-i18next'; -import eco from '@app/assets/icons/emoji/eco.png'; -import fire from '@app/assets/icons/emoji/fire.png'; import { Divider } from '@app/components/elements/Divider.tsx'; import { IconButton } from '@app/components/elements/buttons/IconButton.tsx'; import { IoClose } from 'react-icons/io5'; +import { LinearProgress } from '@app/components/elements/LinearProgress.tsx'; -export function CustomPowerLevelsDialog() { - const { t } = useTranslation('settings', { useSuspense: false }); - const [initialised, setInitialised] = useState(false); - const [saved, setSaved] = useState(false); - - const configCpuLevels = useAppConfigStore((s) => s.custom_max_cpu_usage); - const configGpuLevels = useAppConfigStore((s) => s.custom_max_gpu_usage); - const changeMiningMode = useMiningStore((s) => s.changeMiningMode); - - const customLevelsDialogOpen = useMiningStore((s) => s.customLevelsDialogOpen); - const setCustomLevelsDialogOpen = useMiningStore((s) => s.setCustomLevelsDialogOpen); - - const [customGpuLevel, setCustomGpuLevel] = useState(1); - const [customCpuLevel, setCustomCpuLevel] = useState(1); - +const useGetMaxConsumptionLevels = () => { + const [isLoading, setIsLoading] = useState(false); const [maxLevels, setMaxLevels] = useState({ max_cpu_available: 0, max_gpu_available: 0, }); - useEffect(() => { - // Set saved values - if (!customLevelsDialogOpen || initialised) return; - - if (configCpuLevels) { - setCustomCpuLevel(configCpuLevels); - } - - if (configGpuLevels) { - setCustomGpuLevel(configGpuLevels); - } - - setInitialised(true); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [configCpuLevels, configGpuLevels, customLevelsDialogOpen]); - useEffect(() => { // Get max CPU and GPU values const getMaxConsumptionLevels = async () => { + setIsLoading(true); const res = await invoke('get_max_consumption_levels'); setMaxLevels(res); + setIsLoading(false); }; - if (customLevelsDialogOpen && !maxLevels.max_cpu_available) { + if (maxLevels.max_cpu_available === 0 && !isLoading) { getMaxConsumptionLevels(); } - }, [customLevelsDialogOpen, maxLevels.max_cpu_available]); + }, [maxLevels.max_cpu_available, isLoading]); - useEffect(() => { - // Update config with a slight delay - if (!initialised || !customLevelsDialogOpen) return; + return maxLevels; +}; - if (saved) { - setSaved(false); - } +export function CustomPowerLevelsDialog() { + const { t } = useTranslation('settings', { useSuspense: false }); + const [saved, setSaved] = useState(false); - if (customCpuLevel !== configCpuLevels || customGpuLevel !== configGpuLevels) { - const timeout = setTimeout(() => { - changeMiningMode({ - mode: 'Custom', - customCpuLevels: customCpuLevel, - customGpuLevels: customGpuLevel, - }).then(() => setSaved(true)); - }, 800); - return () => clearTimeout(timeout); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [customCpuLevel, customGpuLevel]); + const mode = useAppConfigStore((s) => s.mode); + const configCpuLevels = useAppConfigStore((s) => s.custom_max_cpu_usage); + const configGpuLevels = useAppConfigStore((s) => s.custom_max_gpu_usage); + + const customLevelsDialogOpen = useMiningStore((s) => s.customLevelsDialogOpen); + const setCustomLevelsDialogOpen = useMiningStore((s) => s.setCustomLevelsDialogOpen); + + const changeMiningMode = useMiningStore((s) => s.changeMiningMode); + + const maxLevels = useGetMaxConsumptionLevels(); useEffect(() => { // Remove save animation @@ -103,6 +74,44 @@ export function CustomPowerLevelsDialog() { } }, [saved]); + const gpuValue = useMemo(() => { + if (mode !== 'Custom' && maxLevels.max_gpu_available) { + return mode === 'Eco' ? 3 : Math.min(maxLevels.max_gpu_available, 800); + } + return configGpuLevels || 0; + }, [mode, maxLevels.max_gpu_available, configGpuLevels]); + + const cpuValue = useMemo(() => { + if (mode !== 'Custom' && maxLevels.max_cpu_available) { + return mode === 'Eco' ? Math.round(maxLevels.max_cpu_available * 0.3) : maxLevels.max_cpu_available; + } + return configCpuLevels || 0; + }, [mode, maxLevels.max_cpu_available, configCpuLevels]); + + const handleChangeCpu = useCallback( + (value: number) => { + changeMiningMode({ + mode: 'Custom', + customCpuLevels: value, + customGpuLevels: gpuValue, + }).then(() => setSaved(true)); + }, + [changeMiningMode, gpuValue] + ); + + const handleChangeGpu = useCallback( + (value: number) => { + changeMiningMode({ + mode: 'Custom', + customCpuLevels: cpuValue, + customGpuLevels: value, + }).then(() => setSaved(true)); + }, + [changeMiningMode, cpuValue] + ); + + if (!maxLevels.max_cpu_available) return ; + return ( @@ -120,19 +129,19 @@ export function CustomPowerLevelsDialog() { @@ -145,48 +154,115 @@ interface RangeInputProps { maxLevel: number; value: number; desc: string; - min?: number; warning?: string; onChange: (value: number) => void; } -const RangeInputComponent = ({ label, maxLevel, value, desc, onChange, warning, min = 1 }: RangeInputProps) => { +const RangeInputComponent = ({ label, maxLevel, value, desc, onChange, warning }: RangeInputProps) => { + const min = 1; + const [isHover, setIsHover] = useState(false); const { t } = useTranslation('settings', { useSuspense: true }); - const getPosition = (value: number, max: number) => { + + const [currentValue, setCurrentValue] = useState(0); + const [calculatedValue, setCalculatedValue] = useState(0); + + useEffect(() => { + if (maxLevel && !currentValue) { + setCurrentValue(Math.ceil((value * 100) / maxLevel)); + } + }, [currentValue, maxLevel, value]); + + const maxValue = 100; + + const getPosition = useCallback((value: number, max: number) => { // Position the value bubble in the range input thumb - return 15 + ((value - min) / (max - min)) * (600 - 30); - }; + return 15 + ((value - min) / (max - min)) * (SLIDER_WIDTH - SLIDER_THUMB_WIDTH); + }, []); // Check if the value is over 75% of the max level - const hasWarning = (value * 100) / (maxLevel - min) > 75; + const hasWarning = (currentValue * 100) / maxValue > 75; + + const handleMouseUp = useCallback(() => { + setIsHover(false); + onChange(calculatedValue); + }, [calculatedValue, onChange]); + + const handleMouseDown = () => { + setIsHover(true); + }; + + const handleChange = useCallback( + (event: React.ChangeEvent) => { + const newValue = Number(event.target.value); + setCurrentValue(newValue); + const calculatedValue = Math.ceil((newValue * maxLevel) / 100); + setCalculatedValue(calculatedValue); + }, + [maxLevel] + ); + + // Positioning with useMemo for `RangeValueHolder` + const rangeValueHolderStyle = useMemo( + () => ({ + left: getPosition(currentValue, maxValue), + display: isHover ? 'block' : 'none', + }), + [getPosition, currentValue, maxValue, isHover] + ); + + const ecomarkstyle = useMemo( + () => ({ + display: currentValue > 12 && currentValue < 18 ? 'none' : 'block', + left: 15 + ((15 - min) / (maxValue - min)) * (SLIDER_WIDTH - SLIDER_THUMB_WIDTH), + }), + [currentValue] + ); + + const firemarkstyle = useMemo( + () => ({ + display: currentValue > 72 && currentValue < 78 ? 'none' : 'block', + left: 15 + ((75 - min) / (maxValue - min)) * (SLIDER_WIDTH - SLIDER_THUMB_WIDTH), + }), + [currentValue] + ); + + const rangeValueStyle = useMemo( + () => ({ + background: currentValue + ? `linear-gradient(to right, #813bf5 ${currentValue || 1}%, #ddd ${currentValue || 1}%)` + : '#ddd', + }), + [currentValue] + ); - if (!maxLevel) return null; + if (!maxValue) return null; return (
{label} - - {min} + + {'0 %'} - - - - {value} - + + + {currentValue} onChange(parseInt(e.target.value))} + value={currentValue} + style={rangeValueStyle} + max={maxValue} + min={1} + onChange={handleChange} /> - {maxLevel} + {`${maxValue} %`} - {desc} + {t('custom-power-levels.warning')}: {warning}