Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A lot of rendering refactors related to libraries (and a few extras) #1267

Merged
merged 10 commits into from
May 17, 2022
1 change: 0 additions & 1 deletion electron/legendary/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ export class LegendaryLibrary {

if (fullRefresh) {
try {
logInfo('Refreshing Epic Games...', LogPrefix.Legendary)
Copy link
Collaborator Author

@arielj arielj May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refresh function called right after this line also prints this same string to the logs, it gave the impression that the epic library was getting refreshed twice when it didn't.

await this.refresh()
} catch (error) {
logError(`${error}`, LogPrefix.Legendary)
Expand Down
24 changes: 16 additions & 8 deletions src/components/UI/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { faApple, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import React, { useContext, useMemo } from 'react'
import cx from 'classnames'
import React, { useContext } from 'react'
import { useTranslation } from 'react-i18next'
import { SearchBar } from 'src/components/UI'
import { UE_VERSIONS } from './constants'
import { GameInfo } from 'src/types'
import ContextProvider from 'src/state/ContextProvider'
import { SearchBar } from 'src/components/UI'
import FormControl from '../FormControl'
import { UE_VERSIONS } from './constants'
import { faApple, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Visibility, VisibilityOff } from '@mui/icons-material'
import './index.css'

export interface HeaderProps {
numberOfGames: number
interface ComponentProps {
list: GameInfo[]
}

export default function Header({ numberOfGames }: HeaderProps) {
export default function Header({ list }: ComponentProps) {
const { t } = useTranslation()
const {
category,
Expand All @@ -38,6 +39,13 @@ export default function Header({ numberOfGames }: HeaderProps) {
? t('header.ignore_hidden', 'Ignore Hidden')
: t('header.show_hidden', 'Show Hidden')

const numberOfGames = useMemo(() => {
const dlcCount =
category === 'epic' ? list.filter((lib) => lib.install.is_dlc).length : 0

return list.length - dlcCount
}, [list, category])

return (
<div className="Header">
{category !== 'unreal' && (
Expand Down
4 changes: 3 additions & 1 deletion src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const Backend = new HttpApi(null, {
initGamepad()
initShortcuts()

const storage: Storage = window.localStorage

i18next
// load translation using http -> see /public/locales
// learn more: https://github.com/i18next/i18next-http-backend
Expand All @@ -34,7 +36,7 @@ i18next
interpolation: {
escapeValue: false
},
lng: 'en',
lng: storage.getItem('language') || 'en',
Copy link
Collaborator Author

@arielj arielj May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when selecting a language other than en, the app was loaded in english at boot for a moment and then changed to the user's language, which looked glitchy and triggered an extra re-render

react: {
useSuspense: true
},
Expand Down
17 changes: 5 additions & 12 deletions src/screens/Library/components/GameCard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './index.css'

import React, { useContext, useEffect, useState, CSSProperties } from 'react'
import React, { useContext, CSSProperties, useMemo } from 'react'

import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faRepeat } from '@fortawesome/free-solid-svg-icons'
Expand Down Expand Up @@ -191,23 +191,16 @@ const GameCard = ({
return null
}

const [isHiddenGame, setIsHiddenGame] = useState(false)
const [isFavouriteGame, setIsFavouriteGame] = useState(false)

useEffect(() => {
const found = !!hiddenGames.list.find(
const isHiddenGame = useMemo(() => {
return !!hiddenGames.list.find(
(hiddenGame) => hiddenGame.appName === appName
)

setIsHiddenGame(found)
}, [hiddenGames, appName])

useEffect(() => {
const found = !!favouriteGames.list.find(
const isFavouriteGame = useMemo(() => {
return !!favouriteGames.list.find(
(favouriteGame) => favouriteGame.appName === appName
)

setIsFavouriteGame(found)
}, [favouriteGames, appName])

const isMac = ['osx', 'Mac']
Expand Down
190 changes: 150 additions & 40 deletions src/screens/Library/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import './index.css'

import React, { lazy, useContext, useEffect, useRef, useState } from 'react'
import React, {
lazy,
useContext,
useEffect,
useMemo,
useRef,
useState
} from 'react'

import ContextProvider from 'src/state/ContextProvider'

Expand All @@ -23,13 +30,19 @@ export default function Library(): JSX.Element {
layout,
libraryStatus,
refreshing,
refreshingInTheBackground,
category,
filter,
epic,
gog,
recentGames,
favouriteGames,
libraryTopSection
libraryTopSection,
filterText,
platform,
filterPlatform,
hiddenGames,
showHidden
} = useContext(ContextProvider)

const [showModal, setShowModal] = useState({
Expand Down Expand Up @@ -109,26 +122,127 @@ export default function Library(): JSX.Element {
setInstalling(newInstalling)
}, [libraryStatus])

// select library and sort
let libraryToShow = category === 'epic' ? epic.library : gog.library
libraryToShow = libraryToShow.sort(
(a: { title: string }, b: { title: string }) => {
const filterLibrary = (library: GameInfo[], filter: string) => {
if (!library) {
return []
}

if (filter.includes('UE_')) {
return library.filter((game) => {
if (!game.compatible_apps) {
return false
}
return game.compatible_apps.includes(filter)
})
} else {
switch (filter) {
case 'unreal':
return library.filter(
(game) =>
game.is_ue_project || game.is_ue_asset || game.is_ue_plugin
)
case 'asset':
return library.filter((game) => game.is_ue_asset)
case 'plugin':
return library.filter((game) => game.is_ue_plugin)
case 'project':
return library.filter((game) => game.is_ue_project)
default:
return library.filter((game) => game.is_game)
}
}
}

const filterByPlatform = (library: GameInfo[], filter: string) => {
if (category === 'epic' && platform === 'linux') {
return library.filter((game) => game.is_game)
}

const isMac = ['osx', 'Mac']

switch (filter) {
case 'win':
return library.filter((game) => {
return game.is_installed
? game.install.platform === 'windows'
: process.platform === 'darwin'
? !game.is_mac_native
: !game.is_linux_native
})
case 'mac':
return library.filter((game) => {
return game.is_installed
? isMac.includes(game.install.platform ?? '')
: game.is_mac_native
})
case 'linux':
return library.filter((game) => {
return game.is_installed
? game.install.platform === 'linux'
: game.is_linux_native
})
default:
return library.filter((game) => game.is_game)
}
}

// select library
const libraryToShow = useMemo(() => {
let library = category === 'epic' ? epic.library : gog.library

// filter
try {
const filterRegex = new RegExp(filterText, 'i')
const textFilter = ({ title, app_name }: GameInfo) =>
filterRegex.test(title) || filterRegex.test(app_name)
library = filterByPlatform(
filterLibrary(library, filter).filter(textFilter),
filterPlatform
)
} catch (error) {
console.log(error)
}

// hide hidden
const hiddenGamesAppNames = hiddenGames.list.map((hidden) => hidden.appName)

if (!showHidden) {
library = library.filter(
(game) => !hiddenGamesAppNames.includes(game.app_name)
)
}

// sort
library = library.sort((a: { title: string }, b: { title: string }) => {
const gameA = a.title.toUpperCase().replace('THE ', '')
const gameB = b.title.toUpperCase().replace('THE ', '')
return sortDescending ? (gameA > gameB ? -1 : 1) : gameA < gameB ? -1 : 1
}
)
const installed = libraryToShow.filter((g) => g.is_installed)
const notInstalled = libraryToShow.filter(
(g) => !g.is_installed && !installing.includes(g.app_name)
)
const installingGames = libraryToShow.filter((g) =>
installing.includes(g.app_name)
)
libraryToShow = sortInstalled
? [...installed, ...installingGames, ...notInstalled]
: libraryToShow
})
const installed = library.filter((g) => g.is_installed)
const notInstalled = library.filter(
(g) => !g.is_installed && !installing.includes(g.app_name)
)
const installingGames = library.filter((g) =>
installing.includes(g.app_name)
)
library = sortInstalled
? [...installed, ...installingGames, ...notInstalled]
: library

return library
}, [
category,
epic,
gog,
filter,
filterText,
filterPlatform,
sortDescending,
sortInstalled,
showHidden
])

// top section
const showRecentGames =
libraryTopSection === 'recently_played' &&
!!recentGames.length &&
Expand All @@ -139,29 +253,25 @@ export default function Library(): JSX.Element {
!!favouriteGames.list.length &&
category !== 'unreal'

const favourites: GameInfo[] = []

if (showFavourites) {
const favouriteAppNames = favouriteGames.list.map(
(favourite) => favourite.appName
)
epic.library.forEach((game) => {
if (favouriteAppNames.includes(game.app_name)) favourites.push(game)
})
gog.library.forEach((game) => {
if (favouriteAppNames.includes(game.app_name)) favourites.push(game)
})
}

const dlcCount = epic.library.filter((lib) => lib.install.is_dlc)
const numberOfGames =
category === 'epic'
? epic.library.length - dlcCount.length
: gog.library.length
const favourites: GameInfo[] = useMemo(() => {
const tempArray: GameInfo[] = []
if (showFavourites) {
const favouriteAppNames = favouriteGames.list.map(
(favourite) => favourite.appName
)
epic.library.forEach((game) => {
if (favouriteAppNames.includes(game.app_name)) tempArray.push(game)
})
gog.library.forEach((game) => {
if (favouriteAppNames.includes(game.app_name)) tempArray.push(game)
})
}
return tempArray
}, [showFavourites, favouriteGames, epic, gog])

return (
<>
<Header numberOfGames={numberOfGames} />
<Header list={libraryToShow} />

<div className="listing">
<span id="top" />
Expand All @@ -184,9 +294,9 @@ export default function Library(): JSX.Element {

<h3 className="libraryHeader">{titleWithIcons()}</h3>

{refreshing && <UpdateComponent inline />}
{refreshing && !refreshingInTheBackground && <UpdateComponent inline />}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this refreshingInTheBackground to be able to differenciate when the library was being refreshed in the background and in the foreground


{!refreshing && (
{(!refreshing || refreshingInTheBackground) && (
<GamesList
library={libraryToShow}
layout={layout}
Expand Down
1 change: 1 addition & 0 deletions src/state/ContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const initialContext: ContextType = {
refreshLibrary: async () => Promise.resolve(),
refreshWineVersionInfo: async () => Promise.resolve(),
refreshing: false,
refreshingInTheBackground: true,
isRTL: false,
hiddenGames: {
list: [],
Expand Down
Loading