feat: move new achievement related settings into achievement tab in settings

This commit is contained in:
Moyasee
2025-12-27 22:13:48 +02:00
parent 734f8dbce0
commit 2919109a11
4 changed files with 223 additions and 180 deletions

View File

@@ -56,4 +56,159 @@
&__test-achievement-notification-button {
margin-top: 8px;
}
&__volume-control {
display: flex;
flex-direction: column;
gap: 12px;
label {
font-size: 14px;
color: globals.$muted-color;
}
}
&__volume-slider-wrapper {
display: flex;
align-items: center;
gap: 8px;
width: 200px;
position: relative;
--volume-percent: 0%;
}
&__volume-icon {
color: globals.$muted-color;
flex-shrink: 0;
}
&__volume-value {
font-size: 14px;
color: globals.$body-color;
font-weight: 500;
min-width: 40px;
text-align: right;
flex-shrink: 0;
}
&__volume-slider {
flex: 1;
height: 6px;
border-radius: 3px;
background: globals.$dark-background-color;
outline: none;
-webkit-appearance: none;
appearance: none;
cursor: pointer;
transition: background 0.2s;
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: globals.$muted-color;
cursor: pointer;
border: 2px solid globals.$background-color;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transition: all 0.2s;
margin-top: -6px;
&:hover {
transform: scale(1.1);
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.4);
}
&:active {
transform: scale(1.05);
}
}
&::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: globals.$muted-color;
cursor: pointer;
border: 2px solid globals.$background-color;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
transition: all 0.2s;
margin-top: -6px;
&:hover {
transform: scale(1.1);
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.4);
}
&:active {
transform: scale(1.05);
}
}
&::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
border-radius: 3px;
background: linear-gradient(
to right,
globals.$muted-color 0%,
globals.$muted-color var(--volume-percent),
globals.$dark-background-color var(--volume-percent),
globals.$dark-background-color 100%
);
}
&::-moz-range-track {
width: 100%;
height: 6px;
border-radius: 3px;
background: globals.$dark-background-color;
}
&::-moz-range-progress {
height: 6px;
border-radius: 3px;
background: globals.$muted-color;
}
&:focus {
outline: none;
&::-webkit-slider-thumb {
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
}
&::-moz-range-thumb {
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
}
}
&::-ms-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: globals.$muted-color;
cursor: pointer;
border: 2px solid globals.$background-color;
}
&::-ms-track {
width: 100%;
height: 6px;
background: transparent;
border-color: transparent;
color: transparent;
}
&::-ms-fill-lower {
background: globals.$muted-color;
border-radius: 3px;
}
&::-ms-fill-upper {
background: globals.$dark-background-color;
border-radius: 3px;
}
}
}

View File

@@ -1,10 +1,17 @@
import { useContext, useEffect, useMemo, useState } from "react";
import {
useContext,
useEffect,
useMemo,
useState,
useCallback,
useRef,
} from "react";
import { useTranslation } from "react-i18next";
import { CheckboxField, Button, SelectField } from "@renderer/components";
import { useAppSelector } from "@renderer/hooks";
import { settingsContext } from "@renderer/context";
import "./settings-achievements.scss";
import { QuestionIcon } from "@primer/octicons-react";
import { QuestionIcon, UnmuteIcon } from "@primer/octicons-react";
import { AchievementCustomNotificationPosition } from "@types";
export function SettingsAchievements() {
@@ -23,8 +30,11 @@ export function SettingsAchievements() {
achievementCustomNotificationsEnabled: true,
achievementCustomNotificationPosition:
"top-left" as AchievementCustomNotificationPosition,
achievementSoundVolume: 15,
});
const volumeUpdateTimeoutRef = useRef<NodeJS.Timeout>();
useEffect(() => {
if (userPreferences) {
setForm((prev) => ({
@@ -41,6 +51,9 @@ export function SettingsAchievements() {
userPreferences.achievementCustomNotificationsEnabled ?? true,
achievementCustomNotificationPosition:
userPreferences.achievementCustomNotificationPosition ?? "top-left",
achievementSoundVolume: Math.round(
(userPreferences.achievementSoundVolume ?? 0.15) * 100
),
}));
}
}, [userPreferences]);
@@ -65,6 +78,21 @@ export function SettingsAchievements() {
await updateUserPreferences(values);
};
const handleVolumeChange = useCallback(
(newVolume: number) => {
setForm((prev) => ({ ...prev, achievementSoundVolume: newVolume }));
if (volumeUpdateTimeoutRef.current) {
clearTimeout(volumeUpdateTimeoutRef.current);
}
volumeUpdateTimeoutRef.current = setTimeout(() => {
updateUserPreferences({ achievementSoundVolume: newVolume / 100 });
}, 300);
},
[updateUserPreferences]
);
const handleChangeAchievementCustomNotificationPosition = async (
event: React.ChangeEvent<HTMLSelectElement>
) => {
@@ -194,6 +222,42 @@ export function SettingsAchievements() {
</Button>
</>
)}
{form.achievementNotificationsEnabled && (
<div className="settings-achievements__volume-control">
<label htmlFor="achievement-volume">
{t("achievement_sound_volume")}
</label>
<div className="settings-achievements__volume-slider-wrapper">
<UnmuteIcon
size={16}
className="settings-achievements__volume-icon"
/>
<input
id="achievement-volume"
type="range"
min="0"
max="100"
value={form.achievementSoundVolume}
onChange={(e) => {
const volumePercent = parseInt(e.target.value, 10);
if (!isNaN(volumePercent)) {
handleVolumeChange(volumePercent);
}
}}
className="settings-achievements__volume-slider"
style={
{
"--volume-percent": `${form.achievementSoundVolume}%`,
} as React.CSSProperties
}
/>
<span className="settings-achievements__volume-value">
{form.achievementSoundVolume}%
</span>
</div>
</div>
)}
</div>
</div>
);

View File

@@ -1,6 +1,5 @@
import { useContext, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { QuestionIcon } from "@primer/octicons-react";
import { CheckboxField } from "@renderer/components";
import { useAppSelector } from "@renderer/hooks";
@@ -23,11 +22,8 @@ export function SettingsBehavior() {
disableNsfwAlert: false,
enableAutoInstall: false,
seedAfterDownloadComplete: false,
showHiddenAchievementsDescription: false,
showDownloadSpeedInMegabytes: false,
extractFilesByDefault: true,
enableSteamAchievements: false,
enableAchievementScreenshots: false,
autoplayGameTrailers: true,
hideToTrayOnGameStart: false,
enableNewDownloadOptionsBadges: true,
@@ -46,15 +42,9 @@ export function SettingsBehavior() {
enableAutoInstall: userPreferences.enableAutoInstall ?? false,
seedAfterDownloadComplete:
userPreferences.seedAfterDownloadComplete ?? false,
showHiddenAchievementsDescription:
userPreferences.showHiddenAchievementsDescription ?? false,
showDownloadSpeedInMegabytes:
userPreferences.showDownloadSpeedInMegabytes ?? false,
extractFilesByDefault: userPreferences.extractFilesByDefault ?? true,
enableSteamAchievements:
userPreferences.enableSteamAchievements ?? false,
enableAchievementScreenshots:
userPreferences.enableAchievementScreenshots ?? false,
autoplayGameTrailers: userPreferences.autoplayGameTrailers ?? true,
hideToTrayOnGameStart: userPreferences.hideToTrayOnGameStart ?? false,
enableNewDownloadOptionsBadges:
@@ -186,25 +176,6 @@ export function SettingsBehavior() {
}
/>
<div className={`settings-behavior__checkbox-container--with-tooltip`}>
<CheckboxField
label={t("enable_steam_achievements")}
checked={form.enableSteamAchievements}
onChange={() =>
handleChange({
enableSteamAchievements: !form.enableSteamAchievements,
})
}
/>
<small
className="settings-behavior__checkbox-container--tooltip"
data-open-article="steam-achievements"
>
<QuestionIcon size={12} />
</small>
</div>
<CheckboxField
label={t("enable_new_download_options_badges")}
checked={form.enableNewDownloadOptionsBadges}

View File

@@ -1,11 +1,4 @@
import {
useContext,
useEffect,
useMemo,
useState,
useCallback,
useRef,
} from "react";
import { useContext, useEffect, useMemo, useState } from "react";
import {
TextField,
Button,
@@ -19,9 +12,8 @@ import languageResources from "@locales";
import { orderBy } from "lodash-es";
import { settingsContext } from "@renderer/context";
import "./settings-general.scss";
import { DesktopDownloadIcon, UnmuteIcon } from "@primer/octicons-react";
import { DesktopDownloadIcon } from "@primer/octicons-react";
import { logger } from "@renderer/logger";
import { AchievementCustomNotificationPosition } from "@types";
interface LanguageOption {
option: string;
@@ -46,11 +38,6 @@ export function SettingsGeneral() {
repackUpdatesNotificationsEnabled: false,
friendRequestNotificationsEnabled: false,
friendStartGameNotificationsEnabled: true,
achievementNotificationsEnabled: true,
achievementCustomNotificationsEnabled: true,
achievementCustomNotificationPosition:
"top-left" as AchievementCustomNotificationPosition,
achievementSoundVolume: 15,
language: "",
customStyles: window.localStorage.getItem("customStyles") || "",
});
@@ -59,23 +46,6 @@ export function SettingsGeneral() {
const [defaultDownloadsPath, setDefaultDownloadsPath] = useState("");
const volumeUpdateTimeoutRef = useRef<NodeJS.Timeout>();
const achievementCustomNotificationPositionOptions = useMemo(() => {
return [
"top-left",
"top-center",
"top-right",
"bottom-left",
"bottom-center",
"bottom-right",
].map((position) => ({
key: position,
value: position,
label: t(position),
}));
}, [t]);
useEffect(() => {
window.electron.getDefaultDownloadsPath().then((path) => {
setDefaultDownloadsPath(path);
@@ -106,9 +76,6 @@ export function SettingsGeneral() {
return () => {
clearInterval(interval);
if (volumeUpdateTimeoutRef.current) {
clearTimeout(volumeUpdateTimeoutRef.current);
}
};
}, []);
@@ -132,15 +99,6 @@ export function SettingsGeneral() {
userPreferences.downloadNotificationsEnabled ?? false,
repackUpdatesNotificationsEnabled:
userPreferences.repackUpdatesNotificationsEnabled ?? false,
achievementNotificationsEnabled:
userPreferences.achievementNotificationsEnabled ?? true,
achievementCustomNotificationsEnabled:
userPreferences.achievementCustomNotificationsEnabled ?? true,
achievementCustomNotificationPosition:
userPreferences.achievementCustomNotificationPosition ?? "top-left",
achievementSoundVolume: Math.round(
(userPreferences.achievementSoundVolume ?? 0.15) * 100
),
friendRequestNotificationsEnabled:
userPreferences.friendRequestNotificationsEnabled ?? false,
friendStartGameNotificationsEnabled:
@@ -164,31 +122,6 @@ export function SettingsGeneral() {
await updateUserPreferences(values);
};
const handleVolumeChange = useCallback(
(newVolume: number) => {
setForm((prev) => ({ ...prev, achievementSoundVolume: newVolume }));
if (volumeUpdateTimeoutRef.current) {
clearTimeout(volumeUpdateTimeoutRef.current);
}
volumeUpdateTimeoutRef.current = setTimeout(() => {
updateUserPreferences({ achievementSoundVolume: newVolume / 100 });
}, 300);
},
[updateUserPreferences]
);
const handleChangeAchievementCustomNotificationPosition = async (
event: React.ChangeEvent<HTMLSelectElement>
) => {
const value = event.target.value as AchievementCustomNotificationPosition;
await handleChange({ achievementCustomNotificationPosition: value });
window.electron.updateAchievementCustomNotificationWindow();
};
const handleChooseDownloadsPath = async () => {
const { filePaths } = await window.electron.showOpenDialog({
defaultPath: form.downloadsPath,
@@ -293,86 +226,6 @@ export function SettingsGeneral() {
}
/>
<CheckboxField
label={t("enable_achievement_notifications")}
checked={form.achievementNotificationsEnabled}
onChange={async () => {
await handleChange({
achievementNotificationsEnabled:
!form.achievementNotificationsEnabled,
});
window.electron.updateAchievementCustomNotificationWindow();
}}
/>
<CheckboxField
label={t("enable_achievement_custom_notifications")}
checked={form.achievementCustomNotificationsEnabled}
disabled={!form.achievementNotificationsEnabled}
onChange={async () => {
await handleChange({
achievementCustomNotificationsEnabled:
!form.achievementCustomNotificationsEnabled,
});
window.electron.updateAchievementCustomNotificationWindow();
}}
/>
{form.achievementNotificationsEnabled &&
form.achievementCustomNotificationsEnabled && (
<>
<SelectField
className="settings-general__achievement-custom-notification-position__select-variation"
label={t("achievement_custom_notification_position")}
value={form.achievementCustomNotificationPosition}
onChange={handleChangeAchievementCustomNotificationPosition}
options={achievementCustomNotificationPositionOptions}
/>
<Button
className="settings-general__test-achievement-notification-button"
onClick={() => window.electron.showAchievementTestNotification()}
>
{t("test_notification")}
</Button>
</>
)}
{form.achievementNotificationsEnabled && (
<div className="settings-general__volume-control">
<label htmlFor="achievement-volume">
{t("achievement_sound_volume")}
</label>
<div className="settings-general__volume-slider-wrapper">
<UnmuteIcon size={16} className="settings-general__volume-icon" />
<input
id="achievement-volume"
type="range"
min="0"
max="100"
value={form.achievementSoundVolume}
onChange={(e) => {
const volumePercent = parseInt(e.target.value, 10);
if (!isNaN(volumePercent)) {
handleVolumeChange(volumePercent);
}
}}
className="settings-general__volume-slider"
style={
{
"--volume-percent": `${form.achievementSoundVolume}%`,
} as React.CSSProperties
}
/>
<span className="settings-general__volume-value">
{form.achievementSoundVolume}%
</span>
</div>
</div>
)}
<h2 className="settings-general__section-title">{t("common_redist")}</h2>
<p className="settings-general__common-redist-description">