From 3c6698e1dadc6fec985eb84c973e2c09a6b080b6 Mon Sep 17 00:00:00 2001 From: Ariel Juodziukynas Date: Sun, 1 Oct 2023 15:48:56 -0300 Subject: [PATCH 1/4] [CI] Install node-gyp in CI to fix error related to fontawesome-svg-core (#3099) --- .github/workflows/build-prs-linux.yml | 2 ++ .github/workflows/build-prs-mac.yml | 2 ++ .github/workflows/build.yml | 6 ++++++ .github/workflows/codecheck.yml | 2 ++ .github/workflows/lint.yml | 2 ++ .github/workflows/test-e2e-dev.yml | 4 ++++ .github/workflows/test-e2e-packaged.yml | 4 ++++ .github/workflows/test.yml | 2 ++ 8 files changed, 24 insertions(+) diff --git a/.github/workflows/build-prs-linux.yml b/.github/workflows/build-prs-linux.yml index 5bc15de109..945021733d 100644 --- a/.github/workflows/build-prs-linux.yml +++ b/.github/workflows/build-prs-linux.yml @@ -22,6 +22,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Build artifacts. diff --git a/.github/workflows/build-prs-mac.yml b/.github/workflows/build-prs-mac.yml index 94117b1b67..17eba4a3b7 100644 --- a/.github/workflows/build-prs-mac.yml +++ b/.github/workflows/build-prs-mac.yml @@ -20,6 +20,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Build artifacts. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa2321bd37..5bf3de8318 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,6 +18,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Build artifacts. @@ -39,6 +41,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Upload Snap to Edge Channel. @@ -64,6 +68,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Build artifacts. diff --git a/.github/workflows/codecheck.yml b/.github/workflows/codecheck.yml index c226268702..a67a7085a9 100644 --- a/.github/workflows/codecheck.yml +++ b/.github/workflows/codecheck.yml @@ -14,6 +14,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Check Typescript syntax diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index af16fd3346..ec6eac934c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,6 +14,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Lint code. diff --git a/.github/workflows/test-e2e-dev.yml b/.github/workflows/test-e2e-dev.yml index 82031334f5..4c62b2ee88 100644 --- a/.github/workflows/test-e2e-dev.yml +++ b/.github/workflows/test-e2e-dev.yml @@ -15,6 +15,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile env: @@ -32,6 +34,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile env: diff --git a/.github/workflows/test-e2e-packaged.yml b/.github/workflows/test-e2e-packaged.yml index 7ef39abb12..6b2d19acbf 100644 --- a/.github/workflows/test-e2e-packaged.yml +++ b/.github/workflows/test-e2e-packaged.yml @@ -18,6 +18,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile env: @@ -35,6 +37,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8de82e1313..5fb2efc909 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,8 @@ jobs: with: node-version: '18' cache: 'yarn' + - name: Install node-gyp. + run: yarn global add node-gyp - name: Install modules. run: yarn install --frozen-lockfile - name: Test From dac59650f9a956ef43ca878b1761b66875152ca4 Mon Sep 17 00:00:00 2001 From: Ariel Juodziukynas Date: Sun, 1 Oct 2023 16:26:20 -0300 Subject: [PATCH 2/4] [UX] Don't use linux anticheat info on MacOS (#3063) Show anticheat widget for MacOS --- src/backend/anticheat/utils.ts | 10 +++++---- .../components/UI/Anticheat/index.scss | 3 ++- .../components/UI/Anticheat/index.tsx | 22 ++++++++++++------- .../InstallModal/DownloadDialog/index.tsx | 5 ++++- src/frontend/themes.scss | 1 + 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/backend/anticheat/utils.ts b/src/backend/anticheat/utils.ts index 51f1be149c..e0826ff499 100644 --- a/src/backend/anticheat/utils.ts +++ b/src/backend/anticheat/utils.ts @@ -1,4 +1,4 @@ -import { anticheatDataPath, isWindows } from '../constants' +import { anticheatDataPath, isMac, isWindows } from '../constants' import * as axios from 'axios' import { logInfo, LogPrefix, logWarning } from '../logger/logger' import { readFileSync, writeFileSync } from 'graceful-fs' @@ -9,10 +9,12 @@ async function downloadAntiCheatData() { if (isWindows) return runOnceWhenOnline(async () => { + const url = isMac + ? 'https://raw.githubusercontent.com/Heroic-Games-Launcher/MacAnticheatData/main/games.json' + : 'https://raw.githubusercontent.com/Starz0r/AreWeAntiCheatYet/HEAD/games.json' + try { - const { data } = await axios.default.get( - 'https://raw.githubusercontent.com/Starz0r/AreWeAntiCheatYet/HEAD/games.json' - ) + const { data } = await axios.default.get(url) writeFileSync(anticheatDataPath, JSON.stringify(data, null, 2)) logInfo(`AreWeAntiCheatYet data downloaded`, LogPrefix.Backend) } catch (error) { diff --git a/src/frontend/components/UI/Anticheat/index.scss b/src/frontend/components/UI/Anticheat/index.scss index ccc7dfedc3..af17ead1ac 100644 --- a/src/frontend/components/UI/Anticheat/index.scss +++ b/src/frontend/components/UI/Anticheat/index.scss @@ -23,7 +23,8 @@ margin: 0 0 var(--space-md); } -.anticheatInfo.Denied { +.anticheatInfo.Denied, +.anticheatInfo.Unknown { border: 2px solid var(--anticheat-denied); .statusIcon path { fill: var(--anticheat-denied); diff --git a/src/frontend/components/UI/Anticheat/index.tsx b/src/frontend/components/UI/Anticheat/index.tsx index 1f2f0d3bc4..a5105a8bad 100644 --- a/src/frontend/components/UI/Anticheat/index.tsx +++ b/src/frontend/components/UI/Anticheat/index.tsx @@ -1,4 +1,4 @@ -import React, { MouseEvent } from 'react' +import React, { MouseEvent, useContext } from 'react' import { AntiCheatInfo } from 'common/types' import { createNewWindow } from 'frontend/helpers' @@ -8,6 +8,7 @@ import { ReactComponent as AllowedIcon } from 'frontend/assets/rounded_checkmark import './index.scss' import { useTranslation } from 'react-i18next' +import ContextProvider from 'frontend/state/ContextProvider' type Props = { anticheatInfo: AntiCheatInfo | null @@ -17,12 +18,15 @@ const awacyUrl = 'https://areweanticheatyet.com/' export default function Anticheat({ anticheatInfo }: Props) { const { t } = useTranslation() + const { platform } = useContext(ContextProvider) if (!anticheatInfo) { return null } - const mayNotWork = ['Denied', 'Broken'].includes(anticheatInfo.status) + const mayNotWork = ['Denied', 'Broken', 'Unknown'].includes( + anticheatInfo.status + ) const latestUpdate = anticheatInfo.reference || @@ -81,12 +85,14 @@ export default function Anticheat({ anticheatInfo }: Props) { )} - - {t('anticheat.source', 'Source')}:  - - AreWeAntiCheatYet - - + {platform === 'linux' && ( + + {t('anticheat.source', 'Source')}:  + + AreWeAntiCheatYet + + + )} ) diff --git a/src/frontend/screens/Library/components/InstallModal/DownloadDialog/index.tsx b/src/frontend/screens/Library/components/InstallModal/DownloadDialog/index.tsx index 1b81f35af0..f6cb87d901 100644 --- a/src/frontend/screens/Library/components/InstallModal/DownloadDialog/index.tsx +++ b/src/frontend/screens/Library/components/InstallModal/DownloadDialog/index.tsx @@ -216,7 +216,10 @@ export default function DownloadDialog({ } async function handleInstall(path?: string, ignoreAnticheat = false) { - if (anticheatInfo && ['Denied', 'Broken'].includes(anticheatInfo.status)) { + if ( + anticheatInfo && + ['Denied', 'Broken', 'Unknown'].includes(anticheatInfo.status) + ) { if (!ignoreAnticheat) { confirmInstallBrokenAnticheat(path) return diff --git a/src/frontend/themes.scss b/src/frontend/themes.scss index cf7cfa8528..84c5ff8983 100644 --- a/src/frontend/themes.scss +++ b/src/frontend/themes.scss @@ -13,6 +13,7 @@ body { --anticheat-running: var(--status-default); --anticheat-supported: var(--status-success); --anticheat-planned: var(--status-info); + --anticheat-unknown: var(--status-denied); } body.midnightMirage { From 695f1c660996bbb58007599bac8a8f3a39767958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lidwin?= Date: Tue, 3 Oct 2023 01:46:58 +0200 Subject: [PATCH 3/4] [Improv]: Add opt out of playtime tracking (#3002) * improv: stop registering playtime on suspend, add option to disable playtime tracking * generate i18n * Update src/backend/main.ts Co-authored-by: Ariel Juodziukynas * improv: add help, respect disable sync for pulling game time * rollback suspend changes * avoid confusing log messages for playtime sync --------- Co-authored-by: Ariel Juodziukynas --- public/locales/en/translation.json | 2 + src/backend/main.ts | 24 ++++++++-- src/backend/storeManagers/gog/games.ts | 4 +- src/common/types.ts | 1 + .../Settings/components/PlaytimeSync.tsx | 44 +++++++++++++++++++ .../screens/Settings/components/index.ts | 1 + .../sections/GeneralSettings/index.tsx | 9 ++-- 7 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 src/frontend/screens/Settings/components/PlaytimeSync.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 866e161081..e254c294d1 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -256,6 +256,7 @@ "custom_themes_path": "Do not use CSS files from untrusted sources. When in doubt, ask for a review in our Discord channel.", "custom_themes_wiki": "Check the Wiki for more details on adding custom themes. Click here.", "disable_logs": "Toggle this checkbox ON to disable most of the writes to log files (critical information is always logged). Make sure to turn OFF this setting before reporting any issue.", + "disablePlaytimeSync": "Disables playtime synchronization with given store's servers (currently only GOG is supported)", "dxvk": "DXVK is a Vulkan-based translational layer for DirectX 9, 10 and 11 games. Enabling may improve compatibility. Might cause issues especially for older DirectX games.", "dxvkfpslimit": "Sets a frame rate cap for DXVK games", "dxvknvapi": "DXVK-NVAPI is an implementation of NVAPI built on top of DXVK and the linux native NVAPI, it allows for the usage of DLSS on Nvidia GPUs.", @@ -500,6 +501,7 @@ "defaultWinePrefix": "Set Folder for new Wine Prefixes", "disable_controller": "Disable Heroic navigation using controller", "disable_logs": "Disable Logs", + "disablePlaytimeSync": "Disable playtime synchronization", "discordRPC": "Enable Discord Rich Presence", "download-no-https": "Download games without HTTPS (useful for CDNs e.g. LanCache)", "dxvkfpslimit": "Limit FPS (DX9, 10 and 11)", diff --git a/src/backend/main.ts b/src/backend/main.ts index 54be864db9..6bc2393aae 100644 --- a/src/backend/main.ts +++ b/src/backend/main.ts @@ -336,8 +336,13 @@ if (!gotTheLock) { // Make sure lock is not present when starting up playtimeSyncQueue.delete('lock') - runOnceWhenOnline(syncQueuedPlaytimeGOG) - + if (!settings.disablePlaytimeSync) { + runOnceWhenOnline(syncQueuedPlaytimeGOG) + } else { + logDebug('Skipping playtime sync queue upload - playtime sync disabled', { + prefix: LogPrefix.Backend + }) + } await i18next.use(Backend).init({ backend: { addPath: path.join(publicDir, 'locales', '{{lng}}', '{{ns}}'), @@ -1092,10 +1097,17 @@ ipcMain.handle( sessionPlaytime + tsStore.get(`${appName}.totalPlayed`, 0) tsStore.set(`${appName}.totalPlayed`, Math.floor(totalPlaytime)) + const { disablePlaytimeSync } = GlobalConfig.get().getSettings() if (runner === 'gog') { - await updateGOGPlaytime(appName, startPlayingDate, finishedPlayingDate) + if (!disablePlaytimeSync) { + await updateGOGPlaytime(appName, startPlayingDate, finishedPlayingDate) + } else { + logWarning( + 'Posting playtime session to server skipped - playtime sync disabled', + { prefix: LogPrefix.Backend } + ) + } } - await addRecentGame(game) if (autoSyncSaves && isOnline()) { @@ -1670,6 +1682,10 @@ ipcMain.on('processShortcut', async (e, combination: string) => { ipcMain.handle( 'getPlaytimeFromRunner', async (e, runner, appName): Promise => { + const { disablePlaytimeSync } = GlobalConfig.get().getSettings() + if (disablePlaytimeSync) { + return + } if (runner === 'gog') { return getGOGPlaytime(appName) } diff --git a/src/backend/storeManagers/gog/games.ts b/src/backend/storeManagers/gog/games.ts index 802c3c312a..4175403b6e 100644 --- a/src/backend/storeManagers/gog/games.ts +++ b/src/backend/storeManagers/gog/games.ts @@ -992,7 +992,9 @@ export async function updateGOGPlaytime( return } - const response = await postPlaytimeSession({ ...data, appName }) + const response = await postPlaytimeSession({ ...data, appName }).catch( + () => null + ) if (!response || response.status !== 201) { logError('Failed to post session', { prefix: LogPrefix.Gog }) diff --git a/src/common/types.ts b/src/common/types.ts index b70fe31eee..d7f4c4a38e 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -64,6 +64,7 @@ export interface AppSettings extends GameSettings { defaultSteamPath: string defaultWinePrefix: string disableController: boolean + disablePlaytimeSync: boolean disableLogs: boolean discordRPC: boolean downloadNoHttps: boolean diff --git a/src/frontend/screens/Settings/components/PlaytimeSync.tsx b/src/frontend/screens/Settings/components/PlaytimeSync.tsx new file mode 100644 index 0000000000..c9501b32b5 --- /dev/null +++ b/src/frontend/screens/Settings/components/PlaytimeSync.tsx @@ -0,0 +1,44 @@ +import React, { useContext } from 'react' +import { useTranslation } from 'react-i18next' +import { ToggleSwitch } from 'frontend/components/UI' +import useSetting from 'frontend/hooks/useSetting' +import SettingsContext from '../SettingsContext' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCircleInfo } from '@fortawesome/free-solid-svg-icons' + +const PlaytimeSync = () => { + const { t } = useTranslation() + const { isDefault } = useContext(SettingsContext) + const [disablePlaytimeSync, setDisablePlaytimeSync] = useSetting( + 'disablePlaytimeSync', + false + ) + + if (!isDefault) { + return <> + } + + return ( +
+ setDisablePlaytimeSync(!disablePlaytimeSync)} + title={t( + 'setting.disablePlaytimeSync', + 'Disable playtime synchronization' + )} + /> + +
+ ) +} + +export default PlaytimeSync diff --git a/src/frontend/screens/Settings/components/index.ts b/src/frontend/screens/Settings/components/index.ts index 2716a3966c..eb44a2177a 100644 --- a/src/frontend/screens/Settings/components/index.ts +++ b/src/frontend/screens/Settings/components/index.ts @@ -46,3 +46,4 @@ export { default as WinePrefixesBasePath } from './WinePrefixesBasePath' export { default as WineVersionSelector } from './WineVersionSelector' export { default as WrappersTable } from './WrappersTable' export { default as EnableDXVKFpsLimit } from './EnableDXVKFpsLimit' +export { default as PlaytimeSync } from './PlaytimeSync' diff --git a/src/frontend/screens/Settings/sections/GeneralSettings/index.tsx b/src/frontend/screens/Settings/sections/GeneralSettings/index.tsx index 4ae00df101..f553c295a6 100644 --- a/src/frontend/screens/Settings/sections/GeneralSettings/index.tsx +++ b/src/frontend/screens/Settings/sections/GeneralSettings/index.tsx @@ -19,14 +19,15 @@ import { Shortcuts, TraySettings, UseDarkTrayIcon, - WinePrefixesBasePath + WinePrefixesBasePath, + PlaytimeSync } from '../../components' export default function GeneralSettings() { const { t } = useTranslation() return ( - <> +

{t('settings.navbar.general')}

@@ -57,6 +58,8 @@ export default function GeneralSettings() { + + @@ -66,6 +69,6 @@ export default function GeneralSettings() { - +
) } From 221fa0d7bb2b0dbb5a7d091e1adfd458d0967832 Mon Sep 17 00:00:00 2001 From: Ariel Juodziukynas Date: Tue, 3 Oct 2023 18:46:32 -0300 Subject: [PATCH 4/4] [UI] Redesign Game page (#2884) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Some work for the Game Page redesign * Game Page Redesign: update translation * Game Page Redesign: game scores, report problem, some paddings * Game Page Red: hide non-functional uninstall button * GamePage redesign: some fixes for small windows * feat: add backgrounds to game page * improv: give more control over image load animations to CSS added simple hook that allows to conveniently add load animation to any img component * fix: minor bugs - download size indicators not displayed untill gameInstallInfo was fetched - old design bug - cutoff under new design image on left panel * Game Page Redesign: limit size for big screens * Game Page Redesign: Use cached image for background * Game Page Redesign: Fix anticheat info in big screens --------- Co-authored-by: Paweł Lidwin --- public/locales/en/translation.json | 2 +- src/backend/storeManagers/gog/library.ts | 3 + src/common/types.ts | 3 +- src/frontend/App.css | 9 + src/frontend/App.tsx | 6 +- .../components/UI/Anticheat/index.scss | 3 +- .../components/UI/CachedImage/index.tsx | 13 +- src/frontend/components/UI/Sidebar/index.tsx | 9 +- .../components/GameScore/index.tsx | 62 +- .../GamePage/components/AppleWikiInfo.tsx | 2 +- .../GamePage/components/CloudSavesSync.tsx | 3 +- .../GamePage/components/CompatibilityInfo.tsx | 16 +- .../Game/GamePage/components/Description.tsx | 14 +- .../GamePage/components/DownloadSizeInfo.tsx | 11 +- .../GamePage/components/InstalledInfo.tsx | 112 +- .../Game/GamePage/components/Requirements.tsx | 6 + .../Game/GamePage/components/Scores.tsx | 6 + src/frontend/screens/Game/GamePage/index.scss | 983 ++++++++++++------ src/frontend/screens/Game/GamePage/index.tsx | 242 ++++- .../screens/Game/GamePicture/index.tsx | 7 +- .../screens/Game/GameRequirements/index.css | 4 + .../components/ExperimentalFeatures.tsx | 7 +- src/frontend/state/ContextProvider.tsx | 2 +- src/frontend/state/GlobalState.tsx | 2 +- src/frontend/styles/_buttons.scss | 10 + src/frontend/styles/_colors.scss | 3 + 26 files changed, 1078 insertions(+), 462 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index e254c294d1..e479d8844f 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -530,7 +530,7 @@ "esync": "Enable Esync", "exit-to-tray": "Exit to System Tray", "experimental_features": { - "enableNewShinyFeature": "New shiny feature" + "enableNewDesign": "New design" }, "FsrSharpnessStrenght": "FSR Sharpness Strength", "fsync": "Enable Fsync", diff --git a/src/backend/storeManagers/gog/library.ts b/src/backend/storeManagers/gog/library.ts index 81527ad642..fbf916cc43 100644 --- a/src/backend/storeManagers/gog/library.ts +++ b/src/backend/storeManagers/gog/library.ts @@ -674,6 +674,9 @@ export async function gogToUnifiedInfo( art_square: info.game.vertical_cover.url_format .replace('{formatter}', '') .replace('{ext}', 'jpg'), + art_background: info.game.background.url_format + .replace('{formatter}', '') + .replace('{ext}', 'webp'), cloud_save_enabled: false, extra: { about: { description: info.summary['*'], shortDescription: '' }, diff --git a/src/common/types.ts b/src/common/types.ts index d7f4c4a38e..6080734994 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -44,7 +44,7 @@ export type Release = { } export type ExperimentalFeatures = { - enableNewShinyFeature: boolean // remove this when adding a real experimental feature + enableNewDesign: boolean } export interface AppSettings extends GameSettings { @@ -109,6 +109,7 @@ export interface GameInfo { app_name: string art_cover: string art_logo?: string + art_background?: string art_square: string cloud_save_enabled?: boolean developer?: string diff --git a/src/frontend/App.css b/src/frontend/App.css index 9dcdda975b..f470320761 100644 --- a/src/frontend/App.css +++ b/src/frontend/App.css @@ -112,6 +112,15 @@ svg.disabled { color: var(--background); } +img.loading { + opacity: 0; +} + +img.loaded { + transition: opacity 500ms; + opacity: 1; +} + .react-contextmenu--visible { background-color: var(--input-background); padding: var(--space-md) var(--space-lg); diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index 1effdf67de..dafab46a81 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -19,13 +19,15 @@ import ExternalLinkDialog from './components/UI/ExternalLinkDialog' import classNames from 'classnames' function App() { - const { isSettingsModalOpen, isRTL } = useContext(ContextProvider) + const { isSettingsModalOpen, isRTL, experimentalFeatures } = + useContext(ContextProvider) return (
diff --git a/src/frontend/components/UI/Anticheat/index.scss b/src/frontend/components/UI/Anticheat/index.scss index af17ead1ac..595bd39b24 100644 --- a/src/frontend/components/UI/Anticheat/index.scss +++ b/src/frontend/components/UI/Anticheat/index.scss @@ -11,7 +11,8 @@ .statusInfo { flex-direction: column; display: flex; - padding-inline-start: var(--space-md); + margin-inline: auto; + text-align: start; } } diff --git a/src/frontend/components/UI/CachedImage/index.tsx b/src/frontend/components/UI/CachedImage/index.tsx index e46b66b607..0d4f7f21d9 100644 --- a/src/frontend/components/UI/CachedImage/index.tsx +++ b/src/frontend/components/UI/CachedImage/index.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react' interface CachedImageProps { src: string fallback?: string + className?: string } type Props = React.ImgHTMLAttributes & CachedImageProps @@ -11,6 +12,7 @@ const CachedImage = (props: Props) => { const [useCache, setUseCache] = useState( props.src?.startsWith('http') || false ) + const [loaded, setLoaded] = useState(false) const [useFallback, setUseFallback] = useState(false) const onError = () => { @@ -29,7 +31,16 @@ const CachedImage = (props: Props) => { let src = useFallback ? props.fallback : props.src src = useCache ? `imagecache://${src}` : src - return + return ( + setLoaded(true)} + {...props} + src={src} + onError={onError} + className={`${props.className} ${loaded ? 'loaded' : 'loading'}`} + /> + ) } export default CachedImage diff --git a/src/frontend/components/UI/Sidebar/index.tsx b/src/frontend/components/UI/Sidebar/index.tsx index f2560092ca..f86c098532 100644 --- a/src/frontend/components/UI/Sidebar/index.tsx +++ b/src/frontend/components/UI/Sidebar/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import CurrentDownload from './components/CurrentDownload' import SidebarLinks from './components/SidebarLinks' @@ -8,7 +8,6 @@ import { DMQueueElement } from 'common/types' import { ReactComponent as HeroicIcon } from 'frontend/assets/heroic-icon.svg' import { useNavigate } from 'react-router-dom' -import ContextProvider from 'frontend/state/ContextProvider' let sidebarSize = localStorage.getItem('sidebar-width') || 240 const minWidth = 60 @@ -18,7 +17,6 @@ const collapsedWidth = 120 export default React.memo(function Sidebar() { const sidebarEl = useRef(null) const [currentDMElement, setCurrentDMElement] = useState() - const { experimentalFeatures } = useContext(ContextProvider) const navigate = useNavigate() @@ -146,11 +144,6 @@ export default React.memo(function Sidebar() { )}
- {/* remove this when adding a real experimental feature */} - {experimentalFeatures.enableNewShinyFeature && ( -

New Shiny Feature enabled

- )} - {/* remove */}
) diff --git a/src/frontend/components/UI/WikiGameInfo/components/GameScore/index.tsx b/src/frontend/components/UI/WikiGameInfo/components/GameScore/index.tsx index 1526cf32d1..17ffca3642 100644 --- a/src/frontend/components/UI/WikiGameInfo/components/GameScore/index.tsx +++ b/src/frontend/components/UI/WikiGameInfo/components/GameScore/index.tsx @@ -1,8 +1,9 @@ -import React from 'react' +import React, { useContext } from 'react' import './index.scss' import { PCGamingWikiInfo } from 'common/types' import classNames from 'classnames' import { createNewWindow } from 'frontend/helpers' +import ContextProvider from 'frontend/state/ContextProvider' type Props = { title: string @@ -10,6 +11,8 @@ type Props = { } export default function GameScore({ title, info }: Props) { + const { experimentalFeatures } = useContext(ContextProvider) + if (!info || (!info.metacritic && !info.opencritic && !info.igdb)) { return null } @@ -34,6 +37,63 @@ export default function GameScore({ title, info }: Props) { return null } + if (experimentalFeatures.enableNewDesign) { + return ( + <> + {metacritic.score && ( + { + if (metacritic.urlid) { + createNewWindow( + `https://www.metacritic.com/game/pc/${metacritic.urlid}` + ) + } else { + createNewWindow( + `https://www.metacritic.com/search/all/${title}/results` + ) + } + }} + > + MetaCritic + {`${metacritic.score}`} + + )} + {opencritic.score && ( + { + if (opencritic.urlid) { + createNewWindow( + `https://opencritic.com/game/${opencritic.urlid}` + ) + } + }} + > + OpenCritic + {`${opencritic.score}`} + + )} + {igdb.score && ( + { + if (metacritic.urlid) { + createNewWindow( + `https://www.igdb.com/games/${metacritic.urlid}` + ) + } else { + createNewWindow(`https://www.igdb.com/search?type=1&q=${title}`) + } + }} + > + IGDB + {`${igdb.score}`} + + )} + + ) + } + return ( <>
diff --git a/src/frontend/screens/Game/GamePage/components/AppleWikiInfo.tsx b/src/frontend/screens/Game/GamePage/components/AppleWikiInfo.tsx index f100ad14de..fda84f7891 100644 --- a/src/frontend/screens/Game/GamePage/components/AppleWikiInfo.tsx +++ b/src/frontend/screens/Game/GamePage/components/AppleWikiInfo.tsx @@ -47,7 +47,7 @@ const AppleWikiInfo = ({ gameInfo }: Props) => { }} > - {t('info.apple-gaming-wiki', 'AppleGamingWiki Rating')}:{' '} + {t('info.apple-gaming-wiki', 'AppleGamingWiki Rating')}: {applegamingwiki.crossoverRating.charAt(0).toUpperCase() + applegamingwiki.crossoverRating.slice(1)} diff --git a/src/frontend/screens/Game/GamePage/components/CloudSavesSync.tsx b/src/frontend/screens/Game/GamePage/components/CloudSavesSync.tsx index e33903c5c8..1c0831388e 100644 --- a/src/frontend/screens/Game/GamePage/components/CloudSavesSync.tsx +++ b/src/frontend/screens/Game/GamePage/components/CloudSavesSync.tsx @@ -44,8 +44,7 @@ const CloudSavesSync = ({ gameInfo }: Props) => { className="iconWithText" > - {t('info.syncsaves')} - {': '} + {t('info.syncsaves')}: {autoSyncSaves ? t('enabled') : t('disabled')}

)} diff --git a/src/frontend/screens/Game/GamePage/components/CompatibilityInfo.tsx b/src/frontend/screens/Game/GamePage/components/CompatibilityInfo.tsx index 5773d1030f..5caec44b3c 100644 --- a/src/frontend/screens/Game/GamePage/components/CompatibilityInfo.tsx +++ b/src/frontend/screens/Game/GamePage/components/CompatibilityInfo.tsx @@ -71,10 +71,10 @@ const CompatibilityInfo = ({ gameInfo }: Props) => { className="iconWithText" > - {t( - 'info.protondb-compatibility-info', - 'Proton Compatibility Tier' - )}:{' '} + + {t('info.protondb-compatibility-info', 'Proton Compatibility Tier')} + : + {steamInfo!.compatibilityLevel!.charAt(0).toUpperCase() + steamInfo!.compatibilityLevel!.slice(1)} @@ -82,10 +82,10 @@ const CompatibilityInfo = ({ gameInfo }: Props) => { {hasSteamDeckCompat && ( - {t( - 'info.steamdeck-compatibility-info', - 'SteamDeck Compatibility' - )}: {steamLevelNames[steamInfo?.steamDeckCatagory ?? 3]} + + {t('info.steamdeck-compatibility-info', 'SteamDeck Compatibility')}: + + {steamLevelNames[steamInfo?.steamDeckCatagory ?? 3]} )} diff --git a/src/frontend/screens/Game/GamePage/components/Description.tsx b/src/frontend/screens/Game/GamePage/components/Description.tsx index 6e3b015ed5..01a1c32e14 100644 --- a/src/frontend/screens/Game/GamePage/components/Description.tsx +++ b/src/frontend/screens/Game/GamePage/components/Description.tsx @@ -6,14 +6,14 @@ const Description = () => { const { t } = useTranslation('gamepage') const { gameExtraInfo, runner } = useContext(GameContext) - if (runner === 'sideload') { - return null - } + let description = '' - const description = - gameExtraInfo?.about?.shortDescription || - gameExtraInfo?.about?.description || - t('generic.noDescription', 'No description available') + if (runner !== 'sideload') { + description = + gameExtraInfo?.about?.shortDescription || + gameExtraInfo?.about?.description || + t('generic.noDescription', 'No description available') + } return
{description}
} diff --git a/src/frontend/screens/Game/GamePage/components/DownloadSizeInfo.tsx b/src/frontend/screens/Game/GamePage/components/DownloadSizeInfo.tsx index 0db28e3b79..db37ee76bd 100644 --- a/src/frontend/screens/Game/GamePage/components/DownloadSizeInfo.tsx +++ b/src/frontend/screens/Game/GamePage/components/DownloadSizeInfo.tsx @@ -27,10 +27,6 @@ const DownloadSizeInfo = ({ gameInfo }: Props) => { return null } - if (!gameInstallInfo) { - return null - } - if (gameInfo.thirdPartyManagedApp) { return null } @@ -50,19 +46,16 @@ const DownloadSizeInfo = ({ gameInfo }: Props) => { <>
- {t('game.downloadSize', 'Download Size')} - {': '} + {t('game.downloadSize', 'Download Size')}: {downloadSize ?? `${t('game.getting-download-size', 'Geting download size')}...`}
- {t('game.installSize', 'Install Size')} - {': '} + {t('game.installSize', 'Install Size')}: {installSize ?? `${t('game.getting-install-size', 'Geting install size')}...`}
-
) } diff --git a/src/frontend/screens/Game/GamePage/components/InstalledInfo.tsx b/src/frontend/screens/Game/GamePage/components/InstalledInfo.tsx index d8d2a6fbe4..5bbb29caaf 100644 --- a/src/frontend/screens/Game/GamePage/components/InstalledInfo.tsx +++ b/src/frontend/screens/Game/GamePage/components/InstalledInfo.tsx @@ -4,6 +4,7 @@ import GameContext from '../../GameContext' import { DownloadDone } from '@mui/icons-material' import PopoverComponent from 'frontend/components/UI/PopoverComponent' import { GameInfo } from 'common/types' +import ContextProvider from 'frontend/state/ContextProvider' interface Props { gameInfo: GameInfo @@ -13,6 +14,7 @@ const InstalledInfo = ({ gameInfo }: Props) => { const { t } = useTranslation('gamepage') const { t: t2 } = useTranslation() const { gameSettings, runner, is } = useContext(GameContext) + const { experimentalFeatures } = useContext(ContextProvider) if (!gameInfo.is_installed) { return null @@ -57,6 +59,64 @@ const InstalledInfo = ({ gameInfo }: Props) => { wineVersion.type === 'crossover' ? wineCrossoverBottle : winePrefix } + const info = ( + <> + {!isSideloaded && ( +
+ {t('info.size')}: {install_size} +
+ )} +
+ {t('info.installedPlatform', 'Installed Platform')}:{' '} + {installPlatform === 'osx' ? 'MacOS' : installPlatform} +
+ {!isSideloaded && ( +
+ {t('info.version')}: {version} +
+ )} +
+ {t('info.canRunOffline', 'Online Required')}:{' '} + {t(canRunOffline ? 'box.no' : 'box.yes')} +
+
+ appLocation !== undefined ? window.api.openFolder(appLocation) : {} + } + > + {t('info.path')}:{' '} +
{appLocation}
+
+ {!is.win && !is.native && ( + <> +
+ Wine: {wineName} +
+ {wineVersion && wineType === 'crossover' ? ( +
+ {t2('setting.winecrossoverbottle', 'Bottle')}:{' '} +
{winePrefix}
+
+ ) : ( +
window.api.openFolder(winePrefix)} + > + {t2('setting.wineprefix', 'WinePrefix')}:{' '} +
{winePrefix}
+
+ )} + + )} +
+ + ) + + if (experimentalFeatures.enableNewDesign) { + return info + } + return ( { } > -
- <> - {!isSideloaded && ( -
- {t('info.size')}: {install_size} -
- )} -
- {t('info.installedPlatform', 'Installed Platform')}:{' '} - {installPlatform === 'osx' ? 'MacOS' : installPlatform} -
- {!isSideloaded && ( -
- {t('info.version')}: {version} -
- )} -
- {t('info.canRunOffline', 'Online Required')}:{' '} - {t(canRunOffline ? 'box.no' : 'box.yes')} -
-
- appLocation !== undefined - ? window.api.openFolder(appLocation) - : {} - } - > - {t('info.path')}: {appLocation} -
- {!is.win && !is.native && ( - <> - Wine: {wineName} - {wineVersion && wineType === 'crossover' ? ( -
- {t2('setting.winecrossoverbottle', 'Bottle')}:{' '} - {winePrefix} -
- ) : ( -
window.api.openFolder(winePrefix)} - > - {t2('setting.wineprefix', 'WinePrefix')}: {winePrefix} -
- )} - - )} -
- -
+
{info}
) } diff --git a/src/frontend/screens/Game/GamePage/components/Requirements.tsx b/src/frontend/screens/Game/GamePage/components/Requirements.tsx index a49e0e12af..ff20a26c6d 100644 --- a/src/frontend/screens/Game/GamePage/components/Requirements.tsx +++ b/src/frontend/screens/Game/GamePage/components/Requirements.tsx @@ -4,10 +4,12 @@ import GameContext from '../../GameContext' import { Hardware } from '@mui/icons-material' import PopoverComponent from 'frontend/components/UI/PopoverComponent' import GameRequirements from '../../GameRequirements' +import ContextProvider from 'frontend/state/ContextProvider' const Requirements = () => { const { t } = useTranslation('gamepage') const { gameExtraInfo } = useContext(GameContext) + const { experimentalFeatures } = useContext(ContextProvider) if (!gameExtraInfo) { return null @@ -19,6 +21,10 @@ const Requirements = () => { return null } + if (experimentalFeatures.enableNewDesign) { + return + } + return ( { const { t } = useTranslation('gamepage') const { wikiInfo } = useContext(GameContext) + const { experimentalFeatures } = useContext(ContextProvider) if (!wikiInfo) { return null @@ -33,6 +35,10 @@ const Scores = ({ gameInfo }: Props) => { return null } + if (experimentalFeatures.enableNewDesign) { + return + } + return ( svg { + background-color: var(--body-background); + width: 60px; + height: 60px; + padding: var(--space-3xs); + border-radius: 10px; - svg path { - fill: var(--neutral-06); - transition: fill 0.2s; + &.gogIcon { + padding: 0px var(--space-3xs) var(--space-3xs); + } } + } + } + + .isRTL { + .gameTools { + right: auto !important; + left: 0px; + } + .settings-icon { + right: auto !important; + left: 6px; + } + .store-icon { + left: auto !important; + right: 1rem; + } + } + + .gameConfig { + display: flex; + align-self: center; + max-width: 1000px; + } + + .gamePicture { + flex-basis: 40%; + flex-shrink: 0; + flex-grow: 0; + } + + .backButton { + position: absolute; + top: 12px; + left: 12px; + background: none; + border: none; + color: var(--accent); + + & > .MuiSvgIcon-root { + width: 40px; + height: 40px; + transition: 300ms; &:hover { - svg path { - fill: var(--neutral-05); - } + color: var(--accent-overlay); } } + } - .game-actions { - position: relative; + .isRTL .backButton { + right: 12px; + left: auto; + transform: scaleX(-100%); + } + + .gameInfo { + text-align: initial; + align-self: flex-start; + font-size: var(--text-md); + color: var(--text-default); + display: flex; + flex-direction: column; + flex: 100%; + max-height: 100vh; + overflow-y: auto; + user-select: text; + padding-block: 0 4rem; + padding-inline: 1.2em 2.2em; + + .title { + font-weight: var(--bold); + margin-bottom: 0; + } + + svg { + cursor: text; + } + + & > .selectFieldWrapper.Field { + margin-bottom: var(--space-md); + } + + & > *:not(:first-child) { + max-width: 560px; + } + + .infoWrapper { + color: var(--text-default); + flex-grow: 1; + line-height: 22px; display: flex; flex-direction: column; - .toggle { - align-self: flex-end; - border: none; - color: var(--neutral-06); - font-size: var(--text-2xl); - padding: var(--space-xs-fixed); - cursor: pointer; - position: relative; - transition: color 0.2s; + .iconWithText { + transition: color 300ms; display: flex; - justify-content: center; - width: 44px; - height: 44px; - background: var(--neutral-03); - border-radius: 10px; - } + align-items: center; - &:focus-within .toggle, - &:hover .toggle, - .toggle:hover { - color: var(--neutral-05); + svg { + margin-inline-end: var(--space-3xs); + } } + } - .gameTools { - position: absolute; - opacity: 1; - pointer-events: all; - transition: all 0.2s; - background: var(--navbar-background); - border-radius: 5px; - z-index: 2; - padding: 0; - max-height: 0px; - overflow: hidden; - right: 0; - top: 112%; + .timeContainerLabel, + .iconWithText { + align-self: flex-start; + } + + .developer { + padding: var(--space-md-fixed) 0; + color: var(--text-default); + } + + button { + max-width: 560px; + } + + .summary { + font-family: var(--primary-font-family); + font-style: normal; + font-weight: normal; + padding-bottom: var(--space-sm); + margin-bottom: var(--space-md); + font-size: var(--text-md); + max-width: 560px; + text-overflow: ellipsis; + color: var(--text-default); + overflow: hidden; + -webkit-line-clamp: 5; + -webkit-box-orient: vertical; + overflow: auto; + height: 150px; + line-height: 1.5; + b { + font-weight: 700; } + } - .gameTools .link { - width: 100%; - padding: 0 0 0.5rem; - text-align: start; - white-space: nowrap; - font-size: 1rem; - background: var(--navbar-background); + .info .smallTitle { + font-weight: bold; + } - &:hover { - color: var(--accent-overlay); - } + .reportProblem { + align-self: flex-start; + margin-top: 1.2em; + color: var(--accent); - &:last-child { - padding-bottom: 0; - } + &:hover { + color: var(--accent-overlay); } - &:focus-within .gameTools, - &:hover .gameTools { - opacity: 1; - pointer-events: all; - padding: var(--space-md); - max-height: 100vh; - box-shadow: 4px 5px 5px -2px rgba(0, 0, 0, 0.2); + & svg { + margin-inline-end: 0.4em; } } } - .store-icon { - position: absolute; - left: 1rem; - bottom: 0.5rem; - border-radius: 10px; - padding: var(--space-3xs); - place-content: center; + .gameRequirements { + align-self: flex-start; + overflow: auto; + } + + .gamePageStoreIcon { fill: var(--text-default); + flex-grow: 1; + text-align: end; + padding-inline-end: var(--space-sm); & > svg { - background-color: var(--body-background); - width: 60px; - height: 60px; + width: var(--text-5xl); + height: var(--text-5xl); padding: var(--space-3xs); border-radius: 10px; + } + } - &.gogIcon { - padding: 0px var(--space-3xs) var(--space-3xs); - } + .installProgress[value] { + appearance: none; + width: 169px; + height: 5px; + + &::-webkit-progress-value { + color: var(--success); + animation: animate-stripes 5s linear infinite; } } -} -.isRTL { - .gameTools { - right: auto !important; - left: 0px; + .selectFieldWrapper.Field ~ .buttonsWrapper { + margin-top: 24px; } - .settings-icon { - right: auto !important; - left: 6px; + + .updateText { + cursor: pointer; + + &:hover { + color: var(--text-secondary); + } } - .store-icon { - left: auto !important; - right: 1rem; + + .pictureTimeContainer { + display: flex; + flex-direction: column; + max-height: 400px; } -} -.gameConfig { - display: flex; - align-self: center; - max-width: 1000px; -} + .infoWrapper.itemContainer { + place-content: center; + text-align: initial; + width: 20vw; + min-width: fit-content; + max-width: 290px; + margin-bottom: 12px; + } -.gamePicture { - flex-basis: 40%; - flex-shrink: 0; - flex-grow: 0; -} + .itemTitle { + font-weight: var(--bold); + text-align: center; + } -.backButton { - position: absolute; - top: 12px; - left: 12px; - background: none; - border: none; - color: var(--accent); + .checkBox { + display: flex; + margin-top: 6px; + align-items: center; + } - & > .MuiSvgIcon-root { - width: 40px; - height: 40px; - transition: 300ms; + .dlcTitle { + font-weight: var(--bold); + margin-top: 16px; + } + + .sdlTitle { + cursor: pointer; + font-weight: var(--bold); &:hover { - color: var(--accent-overlay); + color: var(--download-button); } } -} -.isRTL .backButton { - right: 12px; - left: auto; - transform: scaleX(-100%); -} + .MuiIconButton-colorPrimary, + .MuiCheckbox-colorPrimary.Mui-checked { + color: var(--download-button); + } -.gameInfo { - text-align: initial; - align-self: flex-start; - font-size: var(--text-md); - color: var(--text-default); - display: flex; - flex-direction: column; - flex: 100%; - max-height: 100vh; - overflow-y: auto; - user-select: text; - padding-block: 0 4rem; - padding-inline: 1.2em 2.2em; - - .title { - font-weight: var(--bold); - margin-bottom: 0; + .PrivateSwitchBase-root-1 { + padding: var(--space-3xs); } - svg { - cursor: text; + .buttonsWrapper .button { + min-width: max-content; } - & > .selectFieldWrapper.Field { - margin-bottom: var(--space-md); + .buttonWithIcon { + display: flex; + place-items: center; + justify-content: center; + + & > svg { + height: 20px; + } } - & > *:not(:first-child) { - max-width: 560px; + @keyframes animate-stripes { + 100% { + background-position: -100px 0px; + } } +} - .infoWrapper { - color: var(--text-default); - flex-grow: 1; - line-height: 22px; - display: flex; - flex-direction: column; +.App:not(.oldDesign) .gameConfigContainer { + display: flex; + padding: 2vh 2vw; + height: 100%; + gap: min(3vw, 50px); + align-items: flex-start; + max-height: 1100px; + + .backButton, + .showInfo, + .showExtra, + .showRequirements { + justify-self: flex-start; + width: 44px; + height: 44px; + padding: 6px; + background: var(--neutral-03); + border-radius: 10px; + display: grid; + place-items: center; + border: none; - .iconWithText { - transition: color 300ms; - display: flex; - align-items: center; + svg path { + fill: var(--neutral-06); + transition: fill 0.2s; + } - svg { - margin-inline-end: var(--space-3xs); + &:hover { + svg path { + fill: var(--neutral-05); } } } - .timeContainerLabel, - .iconWithText { - align-self: flex-start; + .mainInfoWrapper, + .extraInfoWrapper { + flex-basis: 50%; + display: grid; + grid-template-rows: min-content 1fr; + row-gap: 20px; } - .developer { - padding: var(--space-md-fixed) 0; - color: var(--text-default); - } + .mainInfo, + .tabContent { + outline: 3px solid #8a8a8a; + outline-offset: -4px; + border-radius: 15px; + isolation: isolate; + position: relative; + overflow: hidden; + align-self: stretch; + justify-self: stretch; + display: flex; + flex-direction: column; - button { - max-width: 560px; - } + & > * { + position: relative; + z-index: 2; + text-align: left; + } - .summary { - font-family: var(--primary-font-family); - font-style: normal; - font-weight: normal; - padding-bottom: var(--space-sm); - margin-bottom: var(--space-md); - font-size: var(--text-md); - max-width: 560px; - text-overflow: ellipsis; - color: var(--text-default); - overflow: hidden; - -webkit-line-clamp: 5; - -webkit-box-orient: vertical; - overflow: auto; - height: 150px; - line-height: 1.5; - b { - font-weight: 700; + &::after { + content: ''; + display: block; + inset: 0px; + position: absolute; + z-index: 0; + background-image: linear-gradient( + 180deg, + transparent, + var(--body-background) 65% + ); } } - .info .smallTitle { - font-weight: bold; - } + .mainInfoWrapper { + grid-template-columns: 1fr min-content min-content; + grid-template-areas: 'back settings menu' 'main main main' 'report report report'; + column-gap: 15px; + min-width: 300px; + width: 50%; - .reportProblem { - align-self: flex-start; - margin-top: 1.2em; - color: var(--accent); + .backButton { + grid-area: back; + } - &:hover { - color: var(--accent-overlay); + .game-actions { + grid-area: menu; } - & svg { - margin-inline-end: 0.4em; + .settings-icon { + grid-area: settings; + right: unset; } - } -} -.gameRequirements { - align-self: flex-start; - overflow: auto; -} + .mainInfo { + grid-area: main; + padding: min(25vh, 300px) min(4vw, 70px) min(5vh, 25px); + background-color: var(--body-background); -.gamePageStoreIcon { - fill: var(--text-default); - flex-grow: 1; - text-align: end; - padding-inline-end: var(--space-sm); + .store-icon { + position: absolute; + left: 0.5rem; + top: 0.5rem; + } - & > svg { - width: var(--text-5xl); - height: var(--text-5xl); - padding: var(--space-3xs); - border-radius: 10px; - } -} + .gamePicture { + position: absolute; + left: 2px; + top: 2px; + right: 2px; + z-index: -1; + border-radius: 10px; + overflow: hidden; + + &::after { + content: ''; + display: block; + inset: 0px; + position: absolute; + background-image: linear-gradient( + 180deg, + transparent, + var(--body-background) + ); + } + } -.installProgress[value] { - appearance: none; - width: 169px; - height: 5px; + h1 { + font-size: 34px; + line-height: 39px; + } + + h1, + .developer { + margin-bottom: 10px; + } + + .summary { + overflow: auto; + font-size: 18px; + line-height: 34px; + text-align: left; + flex-grow: 1; + } + + .gameStatus { + text-align: left; + } + + .buttons { + display: grid; + grid-template-columns: 1fr 1fr; + column-gap: 10px; + margin-top: 15px; + + // hiding the `uninstall` button temporarily until + // properly implemented + button:nth-child(2) { + display: none; + } + } + } - &::-webkit-progress-value { - color: var(--success); - animation: animate-stripes 5s linear infinite; + .reportProblem { + grid-area: report; + svg { + margin-inline-end: 0.5rem; + } + } } -} -.selectFieldWrapper.Field ~ .buttonsWrapper { - margin-top: 24px; -} + .extraInfoWrapper { + .tabs { + display: flex; + gap: 15px; + justify-content: flex-end; + } -.updateText { - cursor: pointer; + .tabContent { + padding: 4vh 1.5vw; - &:hover { - color: var(--text-secondary); + & > div { + overflow: auto; + padding: 10px; + } + + &.infoTab, + &.extraTab { + & > div > * { + display: flex; + text-align: end; + font-size: 16px; + line-height: 24px; + height: 56px; + gap: 10px; + padding: 10px 0; + align-items: center; + border-bottom: 1px solid var(--base-06); + + &:last-child { + border-bottom: 0px; + } + + b { + text-align: start; + white-space: nowrap; + flex-grow: 1; + flex-basis: 140px; + flex-shrink: 0; + } + + .truncatedPath { + z-index: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &:hover .truncatedPath, + &:focus .truncatedPath { + overflow: visible; + white-space: normal; + background: var(--body-background); + } + } + } + } + + .anticheatInfo { + max-width: none; + } } -} -.pictureTimeContainer { - display: flex; - flex-direction: column; - max-height: 400px; -} + .mainInfoWrapper, + .extraInfoWrapper { + max-width: 800px; + height: 100%; + } + .mainInfo, + .tabContent { + max-height: 900px; + } -.infoWrapper.itemContainer { - place-content: center; - text-align: initial; - width: 20vw; - min-width: fit-content; - max-width: 290px; - margin-bottom: 12px; -} + .store-icon { + border-radius: 10px; + padding: var(--space-3xs); + place-content: center; + fill: var(--text-default); -.itemTitle { - font-weight: var(--bold); - text-align: center; -} + & > svg { + background-color: var(--body-background); + width: 60px; + height: 60px; + padding: var(--space-3xs); + border-radius: 10px; -.checkBox { - display: flex; - margin-top: 6px; - align-items: center; -} + &.gogIcon { + padding: 0px var(--space-3xs) var(--space-3xs); + } + } + } -.dlcTitle { - font-weight: var(--bold); - margin-top: 16px; -} + .settings-icon { + width: 44px; + position: relative; + padding: 6px; + right: 6px; + background: var(--neutral-03); + border-radius: 10px; + display: grid; + place-items: center; + height: 44px; -.sdlTitle { - cursor: pointer; - font-weight: var(--bold); + svg path { + fill: var(--neutral-06); + transition: fill 0.2s; + } - &:hover { - color: var(--download-button); + &:hover { + svg path { + fill: var(--neutral-05); + } + } } -} -.MuiIconButton-colorPrimary, -.MuiCheckbox-colorPrimary.Mui-checked { - color: var(--download-button); -} + .game-actions { + position: relative; + display: flex; + flex-direction: column; -.PrivateSwitchBase-root-1 { - padding: var(--space-3xs); -} + .toggle { + align-self: flex-end; + border: none; + color: var(--neutral-06); + font-size: var(--text-2xl); + padding: var(--space-xs-fixed); + cursor: pointer; + position: relative; + transition: color 0.2s; + display: flex; + justify-content: center; + width: 44px; + height: 44px; + background: var(--neutral-03); + border-radius: 10px; + } -.buttonsWrapper .button { - min-width: max-content; -} + &:focus-within .toggle, + &:hover .toggle, + .toggle:hover { + color: var(--neutral-05); + } -.buttonWithIcon { - display: flex; - place-items: center; - justify-content: center; + .gameTools { + position: absolute; + opacity: 1; + pointer-events: all; + transition: all 0.2s; + background: var(--navbar-background); + border-radius: 5px; + z-index: 2; + padding: 0; + max-height: 0px; + overflow: hidden; + right: 0; + top: 112%; + } - & > svg { - height: 20px; + .gameTools .link { + width: 100%; + padding: 0 0 0.5rem; + text-align: start; + white-space: nowrap; + font-size: 1rem; + background: var(--navbar-background); + + &:hover { + color: var(--accent-overlay); + } + + &:last-child { + padding-bottom: 0; + } + } + + &:focus-within .gameTools, + &:hover .gameTools { + opacity: 1; + pointer-events: all; + padding: var(--space-md); + max-height: 100vh; + box-shadow: 4px 5px 5px -2px rgba(0, 0, 0, 0.2); + } } -} -@keyframes animate-stripes { - 100% { - background-position: -100px 0px; + & .backgroundImage { + position: absolute; + z-index: -1; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + overflow: hidden; + object-fit: cover; + filter: blur(15px) brightness(30%); } } diff --git a/src/frontend/screens/Game/GamePage/index.tsx b/src/frontend/screens/Game/GamePage/index.tsx index bbca953d49..8c70555bcd 100644 --- a/src/frontend/screens/Game/GamePage/index.tsx +++ b/src/frontend/screens/Game/GamePage/index.tsx @@ -2,7 +2,13 @@ import './index.scss' import React, { useContext, useEffect, useState } from 'react' -import ArrowCircleLeftIcon from '@mui/icons-material/ArrowCircleLeft' +import { + ArrowCircleLeft, + ArrowBackIosNew, + Info, + Star, + Monitor +} from '@mui/icons-material' import { getGameInfo, getInstallInfo, @@ -13,7 +19,7 @@ import { import { NavLink, useLocation, useParams } from 'react-router-dom' import { useTranslation } from 'react-i18next' import ContextProvider from 'frontend/state/ContextProvider' -import { UpdateComponent } from 'frontend/components/UI' +import { CachedImage, UpdateComponent } from 'frontend/components/UI' import { ExtraInfo, @@ -79,6 +85,7 @@ export default React.memo(function GamePage(): JSX.Element | null { platform, showDialogModal, isSettingsModalOpen, + experimentalFeatures, connectivity } = useContext(ContextProvider) @@ -128,6 +135,8 @@ export default React.memo(function GamePage(): JSX.Element | null { const storage: Storage = window.localStorage + const [tab, setTab] = useState<'info' | 'extra' | 'requirements'>('info') + useEffect(() => { const updateGameInfo = async () => { if (status) { @@ -219,6 +228,8 @@ export default React.memo(function GamePage(): JSX.Element | null { runner, title, art_square, + art_cover, + art_background, install: { platform: installPlatform }, is_installed } = gameInfo @@ -282,8 +293,24 @@ export default React.memo(function GamePage(): JSX.Element | null { wikiInfo } + const hasWikiInfo = + wikiInfo?.applegamingwiki || + wikiInfo?.howlongtobeat || + wikiInfo?.pcgamingwiki?.metacritic.score || + wikiInfo?.pcgamingwiki?.opencritic.score || + wikiInfo?.steamInfo + + const hasRequirements = extraInfo ? extraInfo.reqs.length > 0 : false + return (
+ {!!(art_background ?? art_cover) && + experimentalFeatures.enableNewDesign && ( + + )} {gameInfo.runner !== 'sideload' && showModal.show && ( - - - - -
- -
-
-
-

{title}

- - -
-
- - - - - - - - - - -
- {!notInstallable && ( - - )} - - - - - - -
+ {/* OLD DESIGN */} + {!experimentalFeatures.enableNewDesign && ( + <> + + + + +
+ +
+
+
+

{title}

+ + +
+
+ + + + + + + + + + +
+ {!notInstallable && ( + + )} + + + + + + +
+ + )} + {/* NEW DESIGN */} + {experimentalFeatures.enableNewDesign && ( + <> +
+ + + + + + + +
+ +
+ +
+

{title}

+ + + {!notInstallable && ( + + )} + + +
+ + {gameInfo.is_installed && } +
+
+ +
+
+
+ + {hasWikiInfo && ( + + )} + {hasRequirements && ( + + )} +
+ +
+
+ {tab === 'info' && ( + <> + + + + + )} + {tab === 'extra' && ( + <> + + + + + + )} + {tab === 'requirements' && } +
+
+ + +
+ + )} ) : ( diff --git a/src/frontend/screens/Game/GamePicture/index.tsx b/src/frontend/screens/Game/GamePicture/index.tsx index 2f78a52fbf..c62aa1b8d5 100644 --- a/src/frontend/screens/Game/GamePicture/index.tsx +++ b/src/frontend/screens/Game/GamePicture/index.tsx @@ -4,12 +4,12 @@ import { CachedImage } from 'frontend/components/UI' import './index.css' import fallbackImage from 'frontend/assets/heroic_card.jpg' -type Props = { +interface Props extends React.ImgHTMLAttributes { art_square: string store: string } -function GamePicture({ art_square, store }: Props) { +function GamePicture({ art_square, store, className, ...props }: Props) { function getImageFormatting() { if (art_square === 'fallback' || !art_square) return { src: fallbackImage, fallback: fallbackImage } @@ -29,9 +29,10 @@ function GamePicture({ art_square, store }: Props) {
) diff --git a/src/frontend/screens/Game/GameRequirements/index.css b/src/frontend/screens/Game/GameRequirements/index.css index 0e47b0abdc..577a92dede 100644 --- a/src/frontend/screens/Game/GameRequirements/index.css +++ b/src/frontend/screens/Game/GameRequirements/index.css @@ -29,3 +29,7 @@ table span.text { color: var(--text-secondary); font-size: var(--text-sm); } + +.tabContent .gameRequirements { + width: 100%; +} diff --git a/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx b/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx index b6142f8519..b4406c4f37 100644 --- a/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx +++ b/src/frontend/screens/Settings/components/ExperimentalFeatures.tsx @@ -4,14 +4,13 @@ import useSetting from 'frontend/hooks/useSetting' import { ToggleSwitch } from 'frontend/components/UI' import ContextProvider from 'frontend/state/ContextProvider' -// remove shiny when adding a real experimental feature -const FEATURES = ['enableNewShinyFeature'] +const FEATURES = ['enableNewDesign'] const ExperimentalFeatures = () => { const { t } = useTranslation() const [experimentalFeatures, setExprimentalFeatures] = useSetting( 'experimentalFeatures', - { enableNewShinyFeature: false } // remove this when adding a real experimental feature + { enableNewDesign: false } ) const { handleExperimentalFeatures } = useContext(ContextProvider) @@ -28,7 +27,7 @@ const ExperimentalFeatures = () => { /* Translations: - t('setting.experimental_features.enableNewShinyFeature', 'New shiny feature') // remove this when adding a real experimental feature + t('setting.experimental_features.enableNewDesign', 'New design') */ return ( diff --git a/src/frontend/state/ContextProvider.tsx b/src/frontend/state/ContextProvider.tsx index 5024597ba7..1dce151b13 100644 --- a/src/frontend/state/ContextProvider.tsx +++ b/src/frontend/state/ContextProvider.tsx @@ -87,7 +87,7 @@ const initialContext: ContextType = { setLastChangelogShown: () => null, isSettingsModalOpen: { value: false, type: 'settings' }, setIsSettingsModalOpen: () => null, - experimentalFeatures: { enableNewShinyFeature: false }, // remove this when adding a real experimental feature + experimentalFeatures: { enableNewDesign: false }, handleExperimentalFeatures: () => null } diff --git a/src/frontend/state/GlobalState.tsx b/src/frontend/state/GlobalState.tsx index d3f9dccb90..61a6d7177d 100644 --- a/src/frontend/state/GlobalState.tsx +++ b/src/frontend/state/GlobalState.tsx @@ -201,7 +201,7 @@ class GlobalState extends PureComponent { lastChangelogShown: JSON.parse(storage.getItem('last_changelog') || 'null'), settingsModalOpen: { value: false, type: 'settings', gameInfo: undefined }, experimentalFeatures: globalSettings?.experimentalFeatures || { - enableNewShinyFeature: false // remove this when adding a real experimental feature + enableNewDesign: false } } diff --git a/src/frontend/styles/_buttons.scss b/src/frontend/styles/_buttons.scss index 1b290a04ab..e47940149b 100644 --- a/src/frontend/styles/_buttons.scss +++ b/src/frontend/styles/_buttons.scss @@ -191,3 +191,13 @@ button { color: var(--text-secondary); cursor: not-allowed; } + +.buttonWithIcon { + display: flex; + place-items: center; + justify-content: center; + + & > svg { + height: 20px; + } +} diff --git a/src/frontend/styles/_colors.scss b/src/frontend/styles/_colors.scss index de4b942d2b..4b2b05996c 100644 --- a/src/frontend/styles/_colors.scss +++ b/src/frontend/styles/_colors.scss @@ -25,6 +25,9 @@ --secondary-hover: #a7baff; --secondary-active: #2253ff; + /* Base */ + --base-06: #9aa1a3; + /* Status Hover */ --status-default-hover: rgb(197, 197, 197); --status-success-hover: #14f7a7;