diff --git a/.babelrc b/.babelrc index 34c04d9..c46a138 100644 --- a/.babelrc +++ b/.babelrc @@ -2,6 +2,7 @@ "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": [ ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": false }], - ["@babel/plugin-proposal-class-properties", { "loose": true }] + ["@babel/plugin-proposal-class-properties", { "loose": true }], + "@babel/plugin-transform-runtime" ] -} \ No newline at end of file +} diff --git a/.eslintrc.json b/.eslintrc.json index d41499b..9e35311 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,10 +19,11 @@ "react/jsx-indent": ["error", 2], "class-methods-use-this": ["off"], "no-underscore-dangle": ["off"], - "no-param-reassign": ["error", { "props": false }], + "no-param-reassign": ["off"], "react/forbid-prop-types": ["off"], "jsx-a11y/click-events-have-key-events": ["off"], "jsx-a11y/no-static-element-interactions": ["off"], - "linebreak-style": ["off"] + "linebreak-style": ["off"], + "arrow-body-style": ["off"] } } diff --git a/package-lock.json b/package-lock.json index d9f0d8c..7c22e5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -957,6 +957,17 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-transform-runtime": { + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", + "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", + "semver": "^5.5.1" + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", diff --git a/package.json b/package.json index 9e76f73..81a64b6 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@babel/core": "^7.12.10", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-decorators": "^7.12.12", + "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-react": "^7.12.10", "babel-loader": "^8.2.2", diff --git a/src/js/App.jsx b/src/js/App.jsx index f19d079..342d153 100644 --- a/src/js/App.jsx +++ b/src/js/App.jsx @@ -30,7 +30,6 @@ const { remote } = electron; const log = window.require('electron-log'); log.catchErrors({ showDialog: true }); - window.Steam = Steam; class App extends React.Component { @@ -84,7 +83,10 @@ class App extends React.Component { const navWidth = 48; const { showBack, isMaximized, redirectTo } = this.state; - const navigationTopNodes = [ this.handleNavRedirect('/')} />, this.handleNavRedirect('/import')} />]; + const navigationTopNodes = [ + this.handleNavRedirect('/')} />, + this.handleNavRedirect('/import')} />, + ]; let backBtn; let titleWidth = '100%'; @@ -109,7 +111,7 @@ class App extends React.Component { }} size={22} > - Back + Back ); @@ -181,10 +183,12 @@ class App extends React.Component { + + ); diff --git a/src/js/Components/toastHandler.jsx b/src/js/Components/toastHandler.jsx index ecc12c4..7edbcc2 100644 --- a/src/js/Components/toastHandler.jsx +++ b/src/js/Components/toastHandler.jsx @@ -34,17 +34,21 @@ class ToastHandler extends React.Component { render() { const { toasts } = this.state; - return toasts.slice(0).map((x, i) => ( - {x.toast.logoNode}} - title={x.toast.title} - showCloseIcon - > - {x.toast.contents} - - )); + return ( + <> + {toasts.slice(0).map((x, i) => ( + {x.toast.logoNode}} + title={x.toast.title} + showCloseIcon + > + {x.toast.contents} + + ))} + + ); } } diff --git a/src/js/Import.jsx b/src/js/Import.jsx index 2d41bef..6eeba03 100644 --- a/src/js/Import.jsx +++ b/src/js/Import.jsx @@ -45,7 +45,9 @@ class Import extends React.Component { }; } - componentDidMount() { + async componentDidMount() { + const nonSteamGames = await Steam.getNonSteamGames(); + Promise.all(this.platforms.map((platform) => platform.class.isInstalled())) .then((values) => { // Set .installed @@ -59,7 +61,18 @@ class Import extends React.Component { const getGames = installedPlatforms .reduce((promise, platform) => promise.then(() => { this.setState({ loadingText: `Grabbing games from ${platform.name}...` }); + return platform.class.getGames().then((games) => { + // Filter out any games that are already imported + if (nonSteamGames && nonSteamGames[platform.id]) { + games = games.filter((game) => { + return !nonSteamGames[platform.id].find((nonSteamGame) => { + return nonSteamGame.gameId === game.id; + }); + }); + } + + // nonSteamGames[platform.id].gameId // Populate games array platform.games = games; }); @@ -75,11 +88,20 @@ class Import extends React.Component { installedPlatforms.forEach((platform) => { // Get grids for each platform const ids = platform.games.map((x) => encodeURIComponent(x.id)); - const getGrids = this.SGDB.getGrids({ type: platform.id, id: ids.join(',') }).then((res) => { + + const getGrids = this.SGDB.getGrids({ + type: platform.id, + id: ids.join(','), + dimensions: ['460x215', '920x430'], + }).then((res) => { platform.grids = this._formatResponse(ids, res); - }).catch(() => { - // show an error toast + }).catch((e) => { + log.error('Erorr getting grids from SGDB'); + console.error(e); + // @todo Fallback to text search + // @todo show an error toast }); + gridsPromises.push(getGrids); }); @@ -98,15 +120,25 @@ class Import extends React.Component { saveImportedGames(games) { const gamesStorage = this.store.get('games', {}); + games.forEach((game) => { - gamesStorage[metrohash64(game.exe + (game.params !== 'undefined' ? game.params : ''))] = game; + const key = game.exe + ( + typeof game.params !== 'undefined' + ? game.params + : '' + ); + + const configId = metrohash64(key); + gamesStorage[configId] = game; }); + this.store.set('games', gamesStorage); } // @todo this is horrible but can't be arsed right now _formatResponse(ids, res) { let formatted = false; + // if only single id then return first grid if (ids.length === 1) { if (res.length > 0) { @@ -116,7 +148,9 @@ class Import extends React.Component { // if multiple ids treat each object as a request formatted = res.map((x) => { if (x.success) { - if (x.data[0]) return x.data[0]; + if (x.data[0]) { + return x.data[0]; + } } return false; }); @@ -167,30 +201,32 @@ class Import extends React.Component { const getPosters = this.SGDB.getGrids({ type: platform.id, id: ids.join(','), dimensions: ['600x900'] }).then((res) => { posters = this._formatResponse(ids, res); }).catch(() => { - // show an error toast + // @todo show an error toast }); // Get heroes const getHeroes = this.SGDB.getHeroes({ type: platform.id, id: ids.join(',') }).then((res) => { heroes = this._formatResponse(ids, res); }).catch(() => { - // show an error toast + // @todo show an error toast }); Promise.all([getPosters, getHeroes]).then(() => { const downloadPromises = []; + games.forEach((game, i) => { const appId = Steam.generateNewAppId(game.exe, game.name); // Take (legacy) grids from when we got them for the ImportList const savedGrid = platform.grids[platform.games.indexOf(games[i])]; + if (platform.grids[i] && savedGrid) { const appIdOld = Steam.generateAppId(game.exe, game.name); - const saveGrids = Steam.addAsset('horizontalGrid', appId, savedGrid.url).then((dest) => { - // Symlink to old appid so it works in BPM - Lnf.sync(dest, join(dirname(dest), `${appIdOld}${extname(dest)}`)); - }); - downloadPromises.push(saveGrids); + + downloadPromises.push(Steam.addAsset('horizontalGrid', appId, savedGrid.url)); + + // Old app id is for Big Picture Mode + downloadPromises.push(Steam.addAsset('horizontalGrid', appIdOld, savedGrid.url)); } // Download posters @@ -213,6 +249,8 @@ class Import extends React.Component { }); }); }).catch((err) => { + log.error('Cannot import while Steam is running'); + if (err.type === 'OpenError') { PubSub.publish('toast', { logoNode: 'Error', @@ -257,7 +295,7 @@ class Import extends React.Component { > { installedPlatforms.map((platform) => { - if (!platform.error) { + if (!platform.error && platform.games.length) { return (
{platform.name}
@@ -276,6 +314,7 @@ class Import extends React.Component {
); } + return <>; }) } diff --git a/src/js/Steam.js b/src/js/Steam.js index df13312..171b8a7 100644 --- a/src/js/Steam.js +++ b/src/js/Steam.js @@ -145,8 +145,8 @@ class Steam { const appName = item.appname || item.AppName || item.appName; const exe = item.exe || item.Exe; const appid = this.generateNewAppId(exe, appName); - const configId = metrohash64(exe + item.LaunchOptions); + if (store.has(`games.${configId}`)) { const storedGame = store.get(`games.${configId}`); if (typeof games[storedGame.platform] === 'undefined') { @@ -323,9 +323,10 @@ class Steam { files.forEach((file) => { fs.unlinkSync(file); }); + + fs.writeFileSync(dest, data.read()); + resolve(dest); }); - fs.writeFileSync(dest, data.read()); - resolve(dest); }); }).on('error', (err) => { fs.unlink(dest); @@ -383,8 +384,10 @@ class Steam { static addCategory(games, categoryId) { return new Promise((resolve, reject) => { const levelDBPath = join(process.env.localappdata, 'Steam', 'htmlcache', 'Local Storage', 'leveldb'); + this.getLoggedInUser().then((user) => { const cats = new Categories(levelDBPath, String(user)); + cats.read().then(() => { this.getCurrentUserGridPath().then((userGridPath) => { const localConfigPath = join(userGridPath, '../', 'localconfig.vdf'); @@ -429,7 +432,14 @@ class Steam { localConfig.UserLocalConfigStore.WebStorage['user-collections'] = JSON.stringify(collections).replace(/"/g, '\\"'); // I hate Steam const newVDF = VDF.stringify(localConfig); - fs.writeFileSync(localConfigPath, newVDF); + + try { + fs.writeFileSync(localConfigPath, newVDF); + } catch (e) { + log.error('Error writing categories file'); + console.error(e); + } + cats.close(); return resolve(); });