diff --git a/src/backend/main.ts b/src/backend/main.ts index 6bc2393aae..1e27ee62c5 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -630,7 +630,7 @@ ipcMain.handle('callTool', async (event, { tool, exe, appName, runner }) => { await Winetricks.run(runner, appName, event) break case 'winecfg': - runWineCommandOnGame(runner, appName, { + await runWineCommandOnGame(runner, appName, { gameSettings, commandParts: ['winecfg'], wait: false @@ -639,7 +639,7 @@ ipcMain.handle('callTool', async (event, { tool, exe, appName, runner }) => { case 'runExe': if (exe) { const workingDir = path.parse(exe).dir - runWineCommandOnGame(runner, appName, { + await runWineCommandOnGame(runner, appName, { gameSettings, commandParts: [exe], wait: false, diff --git a/src/frontend/components/UI/Collapsible/Collapsible.tsx b/src/frontend/components/UI/Collapsible/Collapsible.tsx deleted file mode 100644 index b852da18b3..0000000000 --- a/src/frontend/components/UI/Collapsible/Collapsible.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react' - -type Props = { - isOpen: boolean - summary: string - children: React.ReactNode - isCollapsible?: boolean -} - -const Collapsible = ({ isOpen, isCollapsible, children, summary }: Props) => { - return isCollapsible ? ( -
- {summary} - {children} -
- ) : ( -
-

{summary}

- {children} -
- ) -} - -export default Collapsible diff --git a/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx b/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx index dff001dca6..292c479493 100644 --- a/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx +++ b/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx @@ -213,18 +213,23 @@ export default function SidebarLinks() { > {t('settings.navbar.general')} - - - {t('settings.navbar.games_settings_defaults', 'Game Defaults')} - - + {!isWin && ( + + + {t( + 'settings.navbar.games_settings_defaults', + 'Game Defaults' + )} + + + )}

{title}

- + {!isBrowserGame && }
@@ -391,8 +392,7 @@ export default React.memo(function GamePage(): JSX.Element | null { - - + {!isBrowserGame && }
diff --git a/src/frontend/screens/Library/components/GameCard/index.tsx b/src/frontend/screens/Library/components/GameCard/index.tsx index a893446c01..8dc2a2b85d 100644 --- a/src/frontend/screens/Library/components/GameCard/index.tsx +++ b/src/frontend/screens/Library/components/GameCard/index.tsx @@ -117,6 +117,8 @@ const GameCard = ({ const { status, folder, label } = hasStatus(appName, gameInfo, size) + const isBrowserGame = gameInfo.install.platform === 'Browser' + useEffect(() => { setIsLaunching(false) const updateGameInfo = async () => { @@ -311,15 +313,12 @@ const GameCard = ({ // settings label: t('submenu.settings', 'Settings'), onclick: () => setIsSettingsModalOpen(true, 'settings', gameInfo), - show: isInstalled && !isUninstalling + show: isInstalled && !isUninstalling && !isBrowserGame }, { label: t('submenu.logs', 'Logs'), onclick: () => setIsSettingsModalOpen(true, 'log', gameInfo), - show: - isInstalled && - !isUninstalling && - gameInfo.install.platform !== 'Browser' + show: isInstalled && !isUninstalling && !isBrowserGame }, { // hide @@ -385,6 +384,8 @@ const GameCard = ({ ) } + const showSettingsButton = isInstalled && !isUninstalling && !isBrowserGame + return (
{showUninstallModal && ( @@ -464,7 +465,7 @@ const GameCard = ({ )} - {isInstalled && !isUninstalling && ( + {showSettingsButton && ( <> { const { t } = useTranslation() @@ -12,6 +13,11 @@ const BattlEyeRuntime = () => { 'battlEyeRuntime', false ) + const { platform } = useContext(ContextProvider) + + if (platform !== 'linux') { + return null + } const handleBattlEyeRuntime = async () => { if (!battlEyeRuntime) { diff --git a/src/frontend/screens/Settings/components/EacRuntime.tsx b/src/frontend/screens/Settings/components/EacRuntime.tsx index 116fed17ec..ee039b17b0 100644 --- a/src/frontend/screens/Settings/components/EacRuntime.tsx +++ b/src/frontend/screens/Settings/components/EacRuntime.tsx @@ -11,7 +11,11 @@ const EacRuntime = () => { const [installing, setInstalling] = useState(false) const [eacRuntime, setEacRuntime] = useSetting('eacRuntime', false) const [useGameMode, setUseGameMode] = useSetting('useGameMode', false) - const { showDialogModal } = useContext(ContextProvider) + const { showDialogModal, platform } = useContext(ContextProvider) + + if (platform !== 'linux') { + return null + } const handleEacRuntime = async () => { if (!eacRuntime) { diff --git a/src/frontend/screens/Settings/components/SettingsModal/index.scss b/src/frontend/screens/Settings/components/SettingsModal/index.scss index eb7d819789..f2c6ec7d76 100644 --- a/src/frontend/screens/Settings/components/SettingsModal/index.scss +++ b/src/frontend/screens/Settings/components/SettingsModal/index.scss @@ -2,8 +2,9 @@ width: 65vw; display: flex; flex-direction: column; - justify-content: space-between; + flex: 1 1 60vh; max-width: 800px; + min-height: 50vh; .log-box { width: 100%; diff --git a/src/frontend/screens/Settings/components/SettingsModal/index.tsx b/src/frontend/screens/Settings/components/SettingsModal/index.tsx index 1af5db7173..7096cc14a3 100644 --- a/src/frontend/screens/Settings/components/SettingsModal/index.tsx +++ b/src/frontend/screens/Settings/components/SettingsModal/index.tsx @@ -51,7 +51,7 @@ function SettingsModal({ gameInfo, type }: Props) { - {type === 'settings' ? : } + {type === 'settings' ? : } diff --git a/src/frontend/screens/Settings/components/Tools/index.scss b/src/frontend/screens/Settings/components/Tools/index.scss index 1b1a42cfdc..87fcc85919 100644 --- a/src/frontend/screens/Settings/components/Tools/index.scss +++ b/src/frontend/screens/Settings/components/Tools/index.scss @@ -8,6 +8,13 @@ .button { padding-block: var(--space-lg); margin: 0 var(--space-3xs); + display: flex; + align-items: center; + border-radius: 8px; + } + + .active { + animation: fading 1s alternate infinite; } .drag { @@ -19,8 +26,9 @@ flex-direction: column; text-decoration: none; white-space: nowrap; + justify-content: center; - & > span { + & span { transition: 350ms; font-size: var(--text-xs); font-style: italic; @@ -29,3 +37,19 @@ } } } + +@keyframes fading { + 0% { + opacity: 0.2; + } + + 50% { + opacity: 0.5; + background-color: var(--navbar-background); + color: var(--text-default); + } + + 100% { + opacity: 1; + } +} diff --git a/src/frontend/screens/Settings/components/Tools/index.tsx b/src/frontend/screens/Settings/components/Tools/index.tsx index 9b2bf70554..807e94fcdf 100644 --- a/src/frontend/screens/Settings/components/Tools/index.tsx +++ b/src/frontend/screens/Settings/components/Tools/index.tsx @@ -14,6 +14,7 @@ export default function Tools() { const { t } = useTranslation() const [winecfgRunning, setWinecfgRunning] = useState(false) const [winetricksRunning, setWinetricksRunning] = useState(false) + const [runExeRunning, setRunExeRunning] = useState(false) const [progress, setProgress] = useState([]) const { appName, runner, isDefault } = useContext(SettingsContext) const { platform } = useContext(ContextProvider) @@ -25,20 +26,26 @@ export default function Tools() { type Tool = 'winecfg' | 'winetricks' | string async function callTools(tool: Tool, exe?: string) { - if (tool === 'winetricks') { - setWinetricksRunning(true) + const toolStates = { + winetricks: setWinetricksRunning, + winecfg: setWinecfgRunning, + runExe: setRunExeRunning } - if (tool === 'winecfg') { - setWinecfgRunning(true) + + if (tool in toolStates) { + toolStates[tool](true) } + await window.api.callTool({ tool, exe, appName, runner }) - setWinetricksRunning(false) - setWinecfgRunning(false) + + if (tool in toolStates) { + toolStates[tool](false) + } } useEffect(() => { @@ -131,11 +138,12 @@ export default function Tools() { dropHandler(ev)} onDragOver={(ev) => dragOverHandler(ev)} - className="button outline drag" + className={classNames('button outline drag', { + active: runExeRunning + })} onClick={handleRunExe} > - {t('setting.runexe.title')} -
+ {t('setting.runexe.title')}
{t('setting.runexe.message')}
diff --git a/src/frontend/screens/Settings/index.tsx b/src/frontend/screens/Settings/index.tsx index dd85697ee3..182bc22568 100644 --- a/src/frontend/screens/Settings/index.tsx +++ b/src/frontend/screens/Settings/index.tsx @@ -121,7 +121,7 @@ function Settings() { {isGeneralSettings && } - {isGamesSettings && } + {isGamesSettings && } {isSyncSettings && } {isAdvancedSetting && } {isLogSettings && } diff --git a/src/frontend/screens/Settings/sections/GamesSettings/index.scss b/src/frontend/screens/Settings/sections/GamesSettings/index.scss index 6c51b87c15..e1657c3657 100644 --- a/src/frontend/screens/Settings/sections/GamesSettings/index.scss +++ b/src/frontend/screens/Settings/sections/GamesSettings/index.scss @@ -26,6 +26,26 @@ } } +.MuiTabs-root { + padding-bottom: var(--space-xs); + + .MuiTabs-scroller { + .MuiTabs-indicator { + background-color: var(--accent); + } + + .MuiTabs-flexContainer { + .MuiTab-root { + color: var(--text-default); + } + + .Mui-selected { + color: var(--accent); + } + } + } +} + // details, should have an hover effect and a pointer cursor and also the summary should be bold and have a background color details { cursor: pointer; diff --git a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx index 1b2e44f396..fc54773f7e 100644 --- a/src/frontend/screens/Settings/sections/GamesSettings/index.tsx +++ b/src/frontend/screens/Settings/sections/GamesSettings/index.tsx @@ -37,25 +37,95 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faInfoCircle } from '@fortawesome/free-solid-svg-icons' import useSetting from 'frontend/hooks/useSetting' import { defaultWineVersion } from '../..' -import Collapsible from 'frontend/components/UI/Collapsible/Collapsible' import SyncSaves from '../SyncSaves' import FooterInfo from '../FooterInfo' +import { Tabs, Tab } from '@mui/material' +import { GameInfo } from 'common/types' -type Props = { - useDetails?: boolean +type TabPanelProps = { + children?: React.ReactNode + index: string + value: string } -export default function GamesSettings({ useDetails = true }: Props) { +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props + + return ( +
+ {value === index &&
{children}
} +
+ ) +} + +const windowsPlatforms = ['Win32', 'Windows', 'windows'] +function getStartingTab(platform: string, gameInfo?: GameInfo | null): string { + if (!gameInfo) { + if (platform !== 'win32') { + return 'wine' + } + return 'advanced' + } + if (platform === 'win32') { + return 'advanced' + } else if (windowsPlatforms.includes(gameInfo?.install.platform || '')) { + return 'wine' + } else if (platform === 'darwin') { + return 'advanced' + } else { + return 'other' + } +} + +export default function GamesSettings() { const { t } = useTranslation() const { platform } = useContext(ContextProvider) const { isDefault, gameInfo } = useContext(SettingsContext) const [wineVersion] = useSetting('wineVersion', defaultWineVersion) - const [nativeGame, setNativeGame] = useState(false) + const [isNative, setIsNative] = useState(false) const isLinux = platform === 'linux' const isWin = platform === 'win32' const isCrossover = wineVersion?.type === 'crossover' const hasCloudSaves = gameInfo?.cloud_save_enabled && gameInfo.install.platform !== 'linux' + const isBrowserGame = gameInfo?.install.platform === 'Browser' + const isSideloaded = gameInfo?.runner === 'sideload' + + function shouldShowSettings(tab: 'wine' | 'other'): boolean { + if (tab === 'wine') { + if (isWin || isNative || isBrowserGame) { + return false + } + return true + } + + if (isLinux) { + return true + } + return false + } + + // Get the latest used tab index for the current game + const localStorageKey = gameInfo + ? `${gameInfo!.app_name}-setting_tab` + : 'default' + const latestTabIndex = + localStorage.getItem(localStorageKey) || getStartingTab(platform, gameInfo) + const [value, setValue] = useState(latestTabIndex) + + const handleChange = ( + event: React.ChangeEvent, + newValue: string + ) => { + setValue(newValue) + // Store the latest used tab index for the current game + localStorage.setItem(localStorageKey, newValue.toString()) + } useEffect(() => { if (gameInfo) { @@ -64,12 +134,15 @@ export default function GamesSettings({ useDetails = true }: Props) { appName: gameInfo?.app_name, runner: gameInfo?.runner }) - setNativeGame(isNative) + setIsNative(isNative) } getIsNative() } }, []) + const showOtherTab = shouldShowSettings('other') + const showWineTab = shouldShowSettings('wine') + return ( <> {isDefault && ( @@ -82,101 +155,85 @@ export default function GamesSettings({ useDetails = true }: Props) {

)} - {!nativeGame && ( - <> - - - - - - {!isCrossover && ( - <> - - {isLinux && ( - <> - - - - - - - - - )} - - - )} - - - )} - - - - - - - {!nativeGame && } + {showWineTab && } + {showOtherTab && ( + + )} + + + {hasCloudSaves && ( + + )} + - {!isWin && !nativeGame && ( + + + + + {!isCrossover && ( <> - - + {isLinux && ( <> - - - - - - - + + )} + + + + + )} + - - - {isLinux && } - + + {!isNative && } + + + + + {!isNative && ( + <> + + + + )} + - - - - - - - - + + {!isSideloaded && ( + <> + + + + )} + + + + {!isSideloaded && } + - - - - {hasCloudSaves && ( - - - - )} + + + - + {!isDefault && } ) }