diff --git a/.eslintrc.json b/.eslintrc.json index c73f753..5037e76 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,82 +5,20 @@ "node": true, "browser": true }, - "extends": ["eslint:recommended", "plugin:react/recommended"], + "extends": ["airbnb"], "globals": { "Atomics": "readonly", "SharedArrayBuffer": "readonly", "BigInt": true }, - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": [ - "react" - ], "rules": { - "indent": [ - "error", - 4 - ], - "prefer-template": "error", - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "array-bracket-newline": [ - "error", - "consistent" - ], - "no-console": "error", - "function-paren-newline": ["error", "consistent"], - "prefer-arrow-callback": [ - "error", - { - "allowNamedFunctions": false, - "allowUnboundThis": true - } - ], - "arrow-spacing": [ - "error", - { - "before": true, - "after": true - } - ], - "arrow-body-style": [ - "error", - "as-needed", - { - "requireReturnForObjectLiteral": false - } - ], - "arrow-parens": [ - "error", - "always", - { - "requireForBlockBody": true - } - ], - "no-var": "error", - "prefer-const": [ - "error", - { - "destructuring": "any", - "ignoreReadBeforeAssign": true - } - ], - "react/jsx-no-bind": "warn" + "indent": ["error", 2], + "react/jsx-indent": ["error", 2], + "class-methods-use-this": ["off"], + "no-underscore-dangle": ["off"], + "no-param-reassign": ["error", { "props": false }], + "react/forbid-prop-types": ["off"], + "jsx-a11y/click-events-have-key-events": ["off"], + "jsx-a11y/no-static-element-interactions": ["off"] } } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 647637d..ea6a3dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1351,9 +1351,9 @@ } }, "@node-steam/vdf": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@node-steam/vdf/-/vdf-2.0.1.tgz", - "integrity": "sha512-Iu8OCpd0aVM6vH5JZaCpEMvNL5+0c6S7Vv+HwPP3YHWEV9kcOaifJiEchYDGATKGV6SN883n2WBDo6spQUAo3A==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@node-steam/vdf/-/vdf-2.1.0.tgz", + "integrity": "sha512-7UT5bfdUG4QbPni3BIicjl8qQpFIpKXr095sUc6yaVHq52g4uquRD808RdflOXUQW+wDrDyIkJVgucwanO6s0g==" }, "@protobufjs/aspromise": { "version": "1.1.2", @@ -1427,8 +1427,7 @@ "@types/core-js": { "version": "0.9.46", "resolved": "https://registry.npmjs.org/@types/core-js/-/core-js-0.9.46.tgz", - "integrity": "sha512-LooLR6XHes9V+kNYRz1Qm8w3atw9QMn7XeZUmIpUelllF9BdryeUKd/u0Wh5ErcjpWfG39NrToU9MF7ngsTFVw==", - "dev": true + "integrity": "sha512-LooLR6XHes9V+kNYRz1Qm8w3atw9QMn7XeZUmIpUelllF9BdryeUKd/u0Wh5ErcjpWfG39NrToU9MF7ngsTFVw==" }, "@types/debug": { "version": "4.1.5", @@ -1444,8 +1443,7 @@ "@types/marked": { "version": "0.0.28", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-0.0.28.tgz", - "integrity": "sha1-RLp1Tp+lFDJYPo6zCnxN0km1L6o=", - "dev": true + "integrity": "sha1-RLp1Tp+lFDJYPo6zCnxN0km1L6o=" }, "@types/node": { "version": "10.14.4", @@ -1455,14 +1453,12 @@ "@types/prismjs": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.16.0.tgz", - "integrity": "sha512-mEyuziLrfDCQ4juQP1k706BUU/c8OGn/ZFl69AXXY6dStHClKX4P+N8+rhqpul1vRDA2VOygzMRSJJZHyDEOfw==", - "dev": true + "integrity": "sha512-mEyuziLrfDCQ4juQP1k706BUU/c8OGn/ZFl69AXXY6dStHClKX4P+N8+rhqpul1vRDA2VOygzMRSJJZHyDEOfw==" }, "@types/prop-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", - "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==", - "dev": true + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" }, "@types/semver": { "version": "6.0.1", @@ -1472,8 +1468,7 @@ "@types/tinycolor2": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.2.tgz", - "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==", - "dev": true + "integrity": "sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw==" }, "@webassemblyjs/ast": { "version": "1.8.5", @@ -1668,16 +1663,26 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abstract-leveldown": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz", + "integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==", + "requires": { + "level-concat-iterator": "~2.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, "acorn": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", - "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, "acorn-jsx": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", - "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, "ajv": { @@ -1747,13 +1752,10 @@ } }, "ansi-escapes": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.2.1.tgz", - "integrity": "sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==", - "dev": true, - "requires": { - "type-fest": "^0.5.2" - } + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true }, "ansi-regex": { "version": "2.1.1", @@ -1970,6 +1972,16 @@ "sprintf-js": "~1.0.2" } }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", @@ -1991,8 +2003,7 @@ "array-find": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-find/-/array-find-1.0.0.tgz", - "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=", - "dev": true + "integrity": "sha1-bI4obRHtdoMn+OYuzuhzU8o+eLg=" }, "array-find-index": { "version": "1.0.2", @@ -2096,6 +2107,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -2158,6 +2175,15 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, + "axobject-query": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", + "integrity": "sha512-MCeek8ZH7hKyO1rWUbKNQBbl4l2eY0ntk7OGi+q0RlafrCnfPxC06WZA+uebCfmYp4mNU9jRBP1AhGyf8+W3ww==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7" + } + }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -2348,8 +2374,7 @@ "bowser": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", - "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==", - "dev": true + "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==" }, "boxen": { "version": "3.2.0", @@ -2956,12 +2981,12 @@ "dev": true }, "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", "dev": true, "requires": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^2.0.0" } }, "cli-width": { @@ -2974,7 +2999,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", - "dev": true, "optional": true, "requires": { "good-listener": "^1.2.2", @@ -3128,23 +3152,37 @@ } }, "conf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conf/-/conf-5.0.0.tgz", - "integrity": "sha512-lRNyt+iRD4plYaOSVTxu1zPWpaH0EOxgFIR1l3mpC/DGZ7XzhoGFMKmbl54LAgXcSu6knqWgOwdINkqm58N85A==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-6.2.0.tgz", + "integrity": "sha512-fvl40R6YemHrFsNiyP7TD0tzOe3pQD2dfT2s20WvCaq57A1oV+RImbhn2Y4sQGDz1lB0wNSb7dPcPIvQB69YNA==", "requires": { - "ajv": "^6.10.0", + "ajv": "^6.10.2", + "debounce-fn": "^3.0.1", "dot-prop": "^5.0.0", "env-paths": "^2.2.0", - "json-schema-typed": "^7.0.0", + "json-schema-typed": "^7.0.1", "make-dir": "^3.0.0", + "onetime": "^5.1.0", "pkg-up": "^3.0.1", + "semver": "^6.2.0", "write-file-atomic": "^3.0.0" }, "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "dot-prop": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.1.0.tgz", - "integrity": "sha512-n1oC6NBF+KM9oVXtjmen4Yo7HyAVWV2UUl50dCYJdw2924K6dX9bf9TTTWaKtYlRn0FEtxG27KS80ayVLixxJA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "requires": { "is-obj": "^2.0.0" } @@ -3167,15 +3205,23 @@ "semver": "^6.0.0" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, "semver": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", - "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "write-file-atomic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.0.tgz", - "integrity": "sha512-EIgkf60l2oWsffja2Sf2AL384dx328c0B+cIYPTQq5q2rOYuDV00/iPFBOUiDKKwKMOhkymH8AidPaRvzfxY+Q==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -3216,6 +3262,12 @@ } } }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, "console-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", @@ -3236,6 +3288,12 @@ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", "dev": true }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", @@ -3299,16 +3357,14 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", - "dev": true, "requires": { "buffer": "^5.1.0" }, "dependencies": { "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "dev": true, + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", + "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" @@ -3436,7 +3492,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz", "integrity": "sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==", - "dev": true, "requires": { "hyphenate-style-name": "^1.0.2", "isobject": "^3.0.1" @@ -3489,8 +3544,7 @@ "cuint": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", - "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", - "dev": true + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=" }, "currently-unhandled": { "version": "0.4.1", @@ -3507,6 +3561,12 @@ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", "dev": true }, + "damerau-levenshtein": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz", + "integrity": "sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA==", + "dev": true + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3521,6 +3581,14 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, + "debounce-fn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-3.0.1.tgz", + "integrity": "sha512-aBoJh5AhpqlRoHZjHmOzZlRx+wz2xVwGL9rjs+Kj0EWUrL4/h4K7OD176thl2Tdoqui/AaA4xhHrNArGLAaI3Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3539,8 +3607,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "3.3.0", @@ -3569,6 +3636,15 @@ "integrity": "sha512-k09hcQcTDY+cwgiwa6PYKLm3jlagNzQ+RSvhjzESOGOx+MNOuXkxTfEvPrO1IOQ81tArCFYQgi631clB70RpQw==", "dev": true }, + "deferred-leveldown": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", + "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", + "requires": { + "abstract-leveldown": "~6.2.1", + "inherits": "^2.0.3" + } + }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3636,7 +3712,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==", - "dev": true, "optional": true }, "delegates": { @@ -4093,12 +4168,12 @@ } }, "electron-store": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-4.0.0.tgz", - "integrity": "sha512-qgkDetwB9bz+ZA7mNCQGm6zLJOMT4yBkTZ7f16M9iS0GcI/bOeOeFkLkIaJddTtPca7MOiaUM1imMjFqUfQgSA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/electron-store/-/electron-store-5.1.0.tgz", + "integrity": "sha512-uhAF/4+zDb+y0hWqlBirEPEAR4ciCZDp4fRWGFNV62bG+ArdQPpXk7jS0MEVj3CfcG5V7hx7Dpq5oD+1j6GD8Q==", "requires": { - "conf": "^5.0.0", - "type-fest": "^0.5.2" + "conf": "^6.2.0", + "type-fest": "^0.7.1" } }, "electron-to-chromium": { @@ -4193,6 +4268,17 @@ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", "dev": true }, + "encoding-down": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", + "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", + "requires": { + "abstract-leveldown": "^6.2.1", + "inherits": "^2.0.3", + "level-codec": "^9.0.0", + "level-errors": "^2.0.0" + } + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -4223,7 +4309,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, "requires": { "prr": "~1.0.1" } @@ -4238,31 +4323,35 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", + "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", "dev": true, "requires": { "es-to-primitive": "^1.2.0", "function-bind": "^1.1.1", "has": "^1.0.3", + "has-symbols": "^1.0.0", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "object-inspect": "^1.6.0", + "object-keys": "^1.1.1", + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" }, "dependencies": { "object-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true } } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -4277,9 +4366,9 @@ "dev": true }, "eslint": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.1.tgz", - "integrity": "sha512-ES7BzEzr0Q6m5TK9i+/iTpKjclXitOdDK4vT07OqbkBT2/VcN/gO9EL1C4HlK3TAOXYv2ItcmbVR9jO1MR0fJg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.1.0.tgz", + "integrity": "sha512-QhrbdRD7ofuV09IuE2ySWBz0FyXCq0rriLTZXZqaWSI79CVtHVRdkFuFTViiqzZhkCgfOh9USpriuGN2gIpZDQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -4289,9 +4378,9 @@ "debug": "^4.0.1", "doctrine": "^3.0.0", "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.2", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^6.0.0", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", @@ -4368,9 +4457,9 @@ } }, "glob-parent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", - "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -4422,6 +4511,250 @@ } } }, + "eslint-config-airbnb": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.0.1.tgz", + "integrity": "sha512-hLb/ccvW4grVhvd6CT83bECacc+s4Z3/AEyWQdIT2KeTsG9dR7nx1gs7Iw4tDmGKozCNHFn4yZmRm3Tgy+XxyQ==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.0.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-config-airbnb-base": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz", + "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.7", + "object.assign": "^4.1.0", + "object.entries": "^1.1.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", + "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + } + }, + "eslint-module-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz", + "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==", + "dev": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.18.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz", + "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.11.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + } + }, "eslint-plugin-react": { "version": "7.14.3", "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", @@ -4449,9 +4782,9 @@ } }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -4459,6 +4792,12 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz", + "integrity": "sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA==", + "dev": true + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -4470,12 +4809,12 @@ } }, "eslint-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", - "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { @@ -4485,13 +4824,13 @@ "dev": true }, "espree": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.0.tgz", - "integrity": "sha512-boA7CHRLlVWUSg3iL5Kmlt/xT3Q+sXnKoRYYzj1YeM10A76TEJBbotV5pKbnK42hEUIr121zTv+QLRM5LsCPXQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", "dev": true, "requires": { - "acorn": "^7.0.0", - "acorn-jsx": "^5.0.0", + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" } }, @@ -4564,8 +4903,7 @@ "exenv": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=", - "dev": true + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" }, "expand-brackets": { "version": "2.1.4", @@ -4792,8 +5130,7 @@ "fetch-jsonp": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fetch-jsonp/-/fetch-jsonp-1.1.3.tgz", - "integrity": "sha1-nrnlhboIqvcAVjU40Xu+u81aPbI=", - "dev": true + "integrity": "sha1-nrnlhboIqvcAVjU40Xu+u81aPbI=" }, "figgy-pudding": { "version": "3.5.1", @@ -4802,9 +5139,9 @@ "dev": true }, "figures": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.0.0.tgz", - "integrity": "sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -5902,7 +6239,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", - "dev": true, "optional": true, "requires": { "delegate": "^3.1.2" @@ -5946,8 +6282,7 @@ "gud": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==", - "dev": true + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" }, "har-schema": { "version": "2.0.0", @@ -6048,17 +6383,16 @@ } }, "history": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", - "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", - "dev": true, + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", "requires": { "@babel/runtime": "^7.1.2", "loose-envify": "^1.2.0", - "resolve-pathname": "^2.2.0", + "resolve-pathname": "^3.0.0", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0", - "value-equal": "^0.4.0" + "value-equal": "^1.0.1" } }, "hmac-drbg": { @@ -6076,7 +6410,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", - "dev": true, "requires": { "react-is": "^16.7.0" } @@ -6121,8 +6454,7 @@ "hyphenate-style-name": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", - "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==", - "dev": true + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" }, "iconv-lite": { "version": "0.5.0", @@ -6158,10 +6490,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "immediate": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", + "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" + }, "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.0.tgz", + "integrity": "sha512-w1waegcRoBrRQ6kfX2+KWc3lIaVUvHiib7F1ZpggbL5+GyZZrpbOnOVNNuylJonNHO6aCJubjfDUzH6J8HmwSA==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -6234,49 +6571,42 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz", "integrity": "sha1-wVPH6I/YT+9cYC6VqBaLJ3BnH+c=", - "dev": true, "requires": { "bowser": "^1.0.0", "hyphenate-style-name": "^1.0.1" } }, "inquirer": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.1.tgz", - "integrity": "sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", "dev": true, "requires": { - "ansi-escapes": "^4.2.1", + "ansi-escapes": "^3.2.0", "chalk": "^2.4.2", - "cli-cursor": "^3.1.0", + "cli-cursor": "^2.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.15", - "mute-stream": "0.0.8", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^4.1.0", + "string-width": "^2.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, "lodash": { @@ -6286,14 +6616,24 @@ "dev": true }, "string-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.1.0.tgz", - "integrity": "sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^5.2.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "strip-ansi": { @@ -6303,6 +6643,14 @@ "dev": true, "requires": { "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } } } @@ -6611,8 +6959,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -6673,9 +7020,9 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-schema-typed": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.0.tgz", - "integrity": "sha512-ikVqF4dlAgRvAb3MDAgDQRtB/GIC8+iq+z5bczPh9bUT7bAZCdGfGCypJHBquzZNoxebql1UgPxWbImnvkSuJg==" + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.2.tgz", + "integrity": "sha512-40FRIcBSz4y0Ego3gMpbkhtIgebpxKRgW/7i1FfDNL4/xEPQKBM12tKSiCZFNQvad5K4IS3I5Sc8cxza/KSwog==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -6730,9 +7077,9 @@ } }, "jsx-ast-utils": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.1.tgz", - "integrity": "sha512-v3FxCcAf20DayI+uxnCuw795+oOIkVu6EnJ1+kSzhqqTZHNkTZ7B66ZgLp4oLJ/gbA64cI0B7WRoHZMSRdyVRQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -6748,8 +7095,7 @@ "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", - "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=", - "dev": true + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" }, "keyv": { "version": "3.1.0", @@ -6798,6 +7144,112 @@ "invert-kv": "^2.0.0" } }, + "level": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/level/-/level-6.0.0.tgz", + "integrity": "sha512-3oAi7gXLLNr7pHj8c4vbI6lHkXf35m8qb7zWMrNTrOax6CXBVggQAwL1xnC/1CszyYrW3BsLXsY5TMgTxtKfFA==", + "requires": { + "level-js": "^5.0.0", + "level-packager": "^5.1.0", + "leveldown": "^5.4.0", + "opencollective-postinstall": "^2.0.0" + } + }, + "level-codec": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz", + "integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q==" + }, + "level-concat-iterator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", + "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==" + }, + "level-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", + "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", + "requires": { + "errno": "~0.1.1" + } + }, + "level-iterator-stream": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", + "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.4.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "level-js": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.0.tgz", + "integrity": "sha512-BIevs/NlfX1DCbuzt8+p2LCumiuDf6IHq5ehktqmcJVRxzfVdc6lXV2FB5cGkTYg4KAr7WfVkeUBAiTgevy19g==", + "requires": { + "abstract-leveldown": "~6.2.1", + "immediate": "~3.2.3", + "inherits": "^2.0.3", + "ltgt": "^2.1.2" + } + }, + "level-packager": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.0.tgz", + "integrity": "sha512-3pbJmDgGvp/lUQNULPoYQZtUbhMI8KoViYDw7Sa0kWl1mPeHWWJF7T/9upWI/NTMuEikkEE/cd6wBvmrW1+ZnQ==", + "requires": { + "encoding-down": "^6.3.0", + "levelup": "^4.3.2" + } + }, + "level-supports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", + "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", + "requires": { + "xtend": "^4.0.2" + } + }, + "leveldown": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.4.1.tgz", + "integrity": "sha512-3lMPc7eU3yj5g+qF1qlALInzIYnkySIosR1AsUKFjL9D8fYbTLuENBAeDRZXIG4qeWOAyqRItOoLu2v2avWiMA==", + "requires": { + "abstract-leveldown": "~6.2.1", + "napi-macros": "~2.0.0", + "node-gyp-build": "~4.1.0" + } + }, + "levelup": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.3.2.tgz", + "integrity": "sha512-cRTjU4ktWo59wf13PHEiOayHC3n0dOh4i5+FHr4tv4MX9+l7mqETicNq3Aj07HKlLdk0z5muVoDL2RD+ovgiyA==", + "requires": { + "deferred-leveldown": "~5.3.0", + "level-errors": "~2.0.0", + "level-iterator-stream": "~4.0.0", + "level-supports": "~1.0.0", + "xtend": "~4.0.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -6824,8 +7276,7 @@ "load-script": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz", - "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=", - "dev": true + "integrity": "sha1-BJGTngvuVkPuSUp+PaPSuscMbKQ=" }, "loader-runner": { "version": "2.4.0", @@ -6854,9 +7305,9 @@ } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, "lodash.defaults": { "version": "4.2.0", @@ -6896,8 +7347,7 @@ "lodash.omit": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=", - "dev": true + "integrity": "sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=" }, "lodash.union": { "version": "4.6.0", @@ -6944,6 +7394,11 @@ "yallist": "^2.1.2" } }, + "ltgt": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", + "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU=" + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -7001,8 +7456,7 @@ "marked": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", - "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", - "dev": true + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==" }, "md5.js": { "version": "1.3.5", @@ -7132,8 +7586,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "mimic-response": { "version": "1.0.1", @@ -7145,7 +7598,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz", "integrity": "sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw==", - "dev": true, "requires": { "@babel/runtime": "^7.4.0", "gud": "^1.0.0", @@ -7247,13 +7699,12 @@ "murmurhash-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", - "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=", - "dev": true + "integrity": "sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=" }, "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, "nan": { @@ -7282,6 +7733,11 @@ "to-regex": "^3.0.1" } }, + "napi-macros": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", + "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" + }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7326,6 +7782,11 @@ } } }, + "node-gyp-build": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", + "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==" + }, "node-libs-browser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", @@ -7487,6 +7948,12 @@ } } }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, "object-keys": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", @@ -7554,15 +8021,15 @@ } }, "object.fromentries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.0.tgz", - "integrity": "sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.1.tgz", + "integrity": "sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.11.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.15.0", "function-bind": "^1.1.1", - "has": "^1.0.1" + "has": "^1.0.3" } }, "object.pick": { @@ -7595,26 +8062,39 @@ } }, "onetime": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", - "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", "dev": true, "requires": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^1.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + } } }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==" + }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "os-browserify": { @@ -7878,10 +8358,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "requires": { "isarray": "0.0.1" }, @@ -7889,8 +8368,7 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" } } }, @@ -8105,10 +8583,9 @@ } }, "prismjs": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.16.0.tgz", - "integrity": "sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA==", - "dev": true, + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", + "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", "requires": { "clipboard": "^2.0.0" } @@ -8240,8 +8717,7 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" }, "pseudomap": { "version": "1.0.2", @@ -8271,8 +8747,7 @@ "pubsub-js": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/pubsub-js/-/pubsub-js-1.7.0.tgz", - "integrity": "sha512-Pb68P9qFZxnvDipHMuj9oT1FoIgBcXJ9C9eWdHCLZAnulaUoJ3+Y87RhGMYilWpun6DMWVmvK70T4RP4drZMSA==", - "dev": true + "integrity": "sha512-Pb68P9qFZxnvDipHMuj9oT1FoIgBcXJ9C9eWdHCLZAnulaUoJ3+Y87RhGMYilWpun6DMWVmvK70T4RP4drZMSA==" }, "pump": { "version": "3.0.0", @@ -8318,10 +8793,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", - "dev": true, + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.9.0.tgz", + "integrity": "sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -8344,7 +8818,6 @@ "version": "0.19.6", "resolved": "https://registry.npmjs.org/radium/-/radium-0.19.6.tgz", "integrity": "sha512-IABYntqCwYelUUIwA52maSCgJbqtJjHKIoD21wgpw3dGhIUbJ5chDShDGdaFiEzdF03hN9jfQqlmn0bF4YhfrQ==", - "dev": true, "requires": { "array-find": "^1.0.0", "exenv": "^1.2.1", @@ -8406,10 +8879,9 @@ "dev": true }, "react": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.9.0.tgz", - "integrity": "sha512-+7LQnFBwkiw+BobzOF6N//BdoNw0ouwmSJTEm9cglOOmsg/TMiFHZLe2sEoN5M7LgJTj9oHH0gxklfnQe66S1w==", - "dev": true, + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz", + "integrity": "sha512-M5Y8yITaLmU0ynd0r1Yvfq98Rmll6q8AxaEe88c8e7LxO8fZ2cNgmFt0aGAS9wzf1Ao32NKXtCl+/tVVtkxq6g==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -8420,21 +8892,19 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/react-desktop/-/react-desktop-0.3.9.tgz", "integrity": "sha512-vyyEH/mK0P8mU7m/kaD8MeA/7T0R3x19pBGpMY8AROcazJuOuXNBi2fPqNosDNBqQbVLgCcH3py+tciCI+mvTA==", - "dev": true, "requires": { "radium": "=0.19.6" } }, "react-dom": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.9.0.tgz", - "integrity": "sha512-YFT2rxO9hM70ewk9jq0y6sQk8cL02xm4+IzYBz75CQGlClQQ1Bxq0nhHF6OtSbit+AIahujJgb/CPRibFkMNJQ==", - "dev": true, + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.11.0.tgz", + "integrity": "sha512-nrRyIUE1e7j8PaXSPtyRKtz+2y9ubW/ghNgqKFHHAHaeP0fpF5uXR+sq8IMRHC+ZUxw7W9NyCDTBtwWxvkb0iA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.15.0" + "scheduler": "^0.17.0" } }, "react-is": { @@ -8443,10 +8913,9 @@ "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" }, "react-lazyload": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-2.6.2.tgz", - "integrity": "sha512-zbFiwI3H7W0/Qvb6T/ew2NiGe2wj+soYNW7vv5Dte1eZuJDvvyUOHo8GpYfEeWoP5x4Rree2Hwop+lCISalBwg==", - "dev": true + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-2.6.5.tgz", + "integrity": "sha512-C/juO9l7dGS7jEARBLjM3oG7F1lL5bqajz/55sk3GFc0Ippd9vnSkdRxdiaE6gf5si3YxIow8dSJ+YuB2D/3vg==" }, "react-motion": { "version": "0.5.2", @@ -8469,7 +8938,6 @@ "version": "0.20.0", "resolved": "https://registry.npmjs.org/react-player/-/react-player-0.20.0.tgz", "integrity": "sha1-YbphsKDAOpoOXkXucLQPuC6rVr0=", - "dev": true, "requires": { "fetch-jsonp": "^1.0.2", "load-script": "^1.0.0", @@ -8478,10 +8946,9 @@ } }, "react-router": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.1.tgz", - "integrity": "sha512-EM7suCPNKb1NxcTZ2LEOWFtQBQRQXecLxVpdsP4DW4PbbqYWeRiLyV/Tt1SdCrvT2jcyXAXmVTmzvSzrPR63Bg==", - "dev": true, + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.1.2.tgz", + "integrity": "sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", @@ -8496,16 +8963,15 @@ } }, "react-router-dom": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.1.tgz", - "integrity": "sha512-zaVHSy7NN0G91/Bz9GD4owex5+eop+KvgbxXsP/O+iW1/Ln+BrJ8QiIR5a6xNPtrdTvLkxqlDClx13QO1uB8CA==", - "dev": true, + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.1.2.tgz", + "integrity": "sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew==", "requires": { "@babel/runtime": "^7.1.2", "history": "^4.9.0", "loose-envify": "^1.3.1", "prop-types": "^15.6.2", - "react-router": "5.0.1", + "react-router": "5.1.2", "tiny-invariant": "^1.0.2", "tiny-warning": "^1.0.0" } @@ -8513,8 +8979,7 @@ "react-title-component": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/react-title-component/-/react-title-component-1.0.1.tgz", - "integrity": "sha1-z9UUGPRui9QrsBxszKtRuuoPVwA=", - "dev": true + "integrity": "sha1-z9UUGPRui9QrsBxszKtRuuoPVwA=" }, "react-transition-group": { "version": "1.2.1", @@ -8532,7 +8997,6 @@ "version": "1.2.31", "resolved": "https://registry.npmjs.org/react-uwp/-/react-uwp-1.2.31.tgz", "integrity": "sha512-p4J1C4y54oMXeJwKp2pxgNp+Dpe4XC1udUO2z/NLvK4TYD0rFAzXDg3f27qKP2jMVjZhz+uhJUNV2gZmC6T+Yg==", - "dev": true, "requires": { "@types/core-js": "^0.9.41", "@types/marked": "0.0.28", @@ -8556,42 +9020,18 @@ }, "dependencies": { "@types/node": { - "version": "8.10.50", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.50.tgz", - "integrity": "sha512-+ZbcUwJdaBgOZpwXeT0v+gHC/jQbEfzoc9s4d0rN0JIKeQbuTrT+A2n1aQY6LpZjrLXJT7avVUqiCecCJeeZxA==", - "dev": true + "version": "8.10.59", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", + "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" }, "inline-style-prefixer": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz", "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=", - "dev": true, "requires": { "bowser": "^1.7.3", "css-in-js-utils": "^2.0.0" } - }, - "react-transition-group": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", - "dev": true, - "requires": { - "chain-function": "^1.0.0", - "dom-helpers": "^3.2.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.5.6", - "warning": "^3.0.0" - } - }, - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } } } }, @@ -8956,10 +9396,9 @@ "dev": true }, "resolve-pathname": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", - "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" }, "resolve-url": { "version": "0.2.1", @@ -8977,12 +9416,12 @@ } }, "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", "dev": true, "requires": { - "onetime": "^5.1.0", + "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, @@ -9029,9 +9468,9 @@ } }, "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -9071,10 +9510,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "scheduler": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.15.0.tgz", - "integrity": "sha512-xAefmSfN6jqAa7Kuq7LIJY0bwAPG3xlCj0HMEBQk1lxYiDKZscY2xJ5U/61ZTrYbmNQbXa+gc7czPkVo11tnCg==", - "dev": true, + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.17.0.tgz", + "integrity": "sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -9095,7 +9533,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=", - "dev": true, "optional": true }, "semver": { @@ -9414,8 +9851,7 @@ "split-on-first": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "dev": true + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" }, "split-string": { "version": "3.1.0", @@ -9459,8 +9895,7 @@ "stackblur-canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-1.4.1.tgz", - "integrity": "sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs=", - "dev": true + "integrity": "sha1-hJqm+UsnL/JvZHH6QTDtH35HlVs=" }, "stat-mode": { "version": "0.3.0", @@ -9494,6 +9929,14 @@ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, + "steam-categories": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/steam-categories/-/steam-categories-1.1.0.tgz", + "integrity": "sha512-sG6Bdv8iu3LeurHzW7FaGIGmWz6p34jxglH58T2iPJcTs3w/6bzlXXLEollM15auy76rTdjxwrEFGeieq4Ytcw==", + "requires": { + "level": "^6.0.0" + } + }, "steam-id-convertor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/steam-id-convertor/-/steam-id-convertor-1.0.1.tgz", @@ -9532,7 +9975,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/steamid/-/steamid-1.1.3.tgz", "integrity": "sha512-t86YjtP1LtPt8D+TaIARm6PtC9tBnF1FhxQeLFs6ohG7vDUfQuy/M8II14rx1TTUkVuYoWHP/7DlvTtoCGULcw==", - "dev": true, "requires": { "cuint": "^0.2.1" } @@ -9579,8 +10021,7 @@ "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=", - "dev": true + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-width": { "version": "1.0.2", @@ -9592,11 +10033,30 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.trimleft": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", + "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", + "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -9914,26 +10374,22 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==", - "dev": true, "optional": true }, "tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==", - "dev": true + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" }, "tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "dev": true + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, "tinycolor2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", - "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", - "dev": true + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" }, "tmp": { "version": "0.1.0", @@ -10079,9 +10535,9 @@ } }, "type-fest": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.5.2.tgz", - "integrity": "sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==" }, "typedarray": { "version": "0.0.6", @@ -10337,10 +10793,9 @@ } }, "value-equal": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", - "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" }, "verror": { "version": "1.10.0", @@ -10621,10 +11076,10 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, "worker-farm": { @@ -10735,8 +11190,7 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { "version": "4.0.0", diff --git a/package.json b/package.json index e6f9e37..5cb3a80 100644 --- a/package.json +++ b/package.json @@ -67,46 +67,53 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.6", - "crc": "^3.8.0", "css-loader": "^3.2.0", "electron": "^6.0.3", "electron-builder": "^21.2.0", "electron-builder-squirrel-windows": "^21.2.0", "electron-packager": "^14.0.4", - "eslint": "^6.2.1", + "eslint": "^6.1.0", + "eslint-config-airbnb": "^18.0.1", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.14.3", + "eslint-plugin-react-hooks": "^1.7.0", "file-loader": "^4.2.0", - "prop-types": "^15.7.2", - "pubsub-js": "^1.7.0", - "query-string": "^6.8.2", - "react": "^16.9.0", - "react-desktop": "^0.3.9", - "react-dom": "^16.9.0", - "react-router-dom": "^5.0.1", - "react-uwp": "^1.2.31", "steam-id-convertor": "^1.0.1", - "steamid": "^1.1.3", "style-loader": "^1.0.0", "webpack": "^4.39.2", "webpack-cli": "^3.3.7" }, "dependencies": { - "@node-steam/vdf": "^2.0.1", + "@node-steam/vdf": "^2.1.0", "blizzard-product-parser": "^1.0.1", + "crc": "^3.8.0", "electron-log": "^3.0.7", - "electron-store": "^4.0.0", + "electron-store": "^5.1.0", "electron-updater": "^4.1.2", "fuse.js": "^3.4.5", "iconv-lite": "^0.5.0", "js-yaml": "^3.13.1", "jsonminify": "^0.4.1", + "lodash": "^4.17.15", "metrohash": "^2.6.0", "promise-reflect": "^1.1.0", "promise-settle": "^0.3.0", + "prop-types": "^15.7.2", + "pubsub-js": "^1.7.0", + "query-string": "^6.9.0", + "react": "^16.11.0", + "react-desktop": "^0.3.9", + "react-dom": "^16.11.0", + "react-lazyload": "^2.6.5", "react-motion": "^0.5.2", + "react-router-dom": "^5.1.2", "react-transition-group": "1.2.1", + "react-uwp": "^1.2.31", + "steam-categories": "^1.1.0", "steam-shortcut-editor": "^3.1.1", "steamgriddb": "^1.3.0", + "steamid": "^1.1.3", "winreg": "^1.2.4", "xml-js": "^1.6.11" } diff --git a/src/js/App.js b/src/js/App.js deleted file mode 100644 index 6d4b274..0000000 --- a/src/js/App.js +++ /dev/null @@ -1,178 +0,0 @@ -import React from 'react'; -import {TitleBar} from 'react-desktop/windows'; -import {Theme as UWPThemeProvider, getTheme} from 'react-uwp/Theme'; -import NavigationView from 'react-uwp/NavigationView'; -import SplitViewCommand from 'react-uwp/SplitViewCommand'; -import {IconButton} from 'react-uwp'; -import ToastHandler from './toastHandler.js'; -import PubSub from 'pubsub-js'; -import {HashRouter as Router, Redirect, Link, Route} from 'react-router-dom'; - -import UWPNoise from '../img/uwp-noise.png'; - -import Search from './Search.js'; -import Games from './games.js'; -import Import from './Import.js'; - -// Using window.require so babel doesn't change the node require -const electron = window.require('electron'); -const remote = electron.remote; - -// Log renderer errors -const log = window.require('electron-log'); -log.catchErrors({showDialog: true}); - -import '../css/App.css'; - -import Steam from './Steam.js'; -window.Steam = Steam; - -class App extends React.Component { - constructor(props) { - super(props); - - this.state = {isMaximized: false, showBack: false}; - this.toggleMaximize = this.toggleMaximize.bind(this); - - //Track windows snap calling maximize / unmaximize - const window = remote.getCurrentWindow(); - - window.on('maximize', () => { - this.setState({ isMaximized: true }); - }); - - window.on('unmaximize', () => { - this.setState({ isMaximized: false }); - }); - - PubSub.subscribe('showBack', (message, args) => { - this.setState({showBack: args}); - }); - } - - close() { - const window = remote.getCurrentWindow(); - window.close(); - } - - minimize() { - const window = remote.getCurrentWindow(); - window.minimize(); - } - - toggleMaximize() { - const window = remote.getCurrentWindow(); - this.setState({ isMaximized: !this.state.isMaximized }); - if(!this.state.isMaximized) { - window.maximize(); - } else { - window.unmaximize(); - } - } - - handleNavRedirect(path) { - this.setState({redirectTo: path}); - } - - render() { - const accentColor = remote.systemPreferences.getAccentColor(); - const navWidth = 48; - - const navigationTopNodes = [ - this.handleNavRedirect('/')} />, - this.handleNavRedirect('/import')} /> - ]; - - let backBtn; - let titleWidth = '100%'; - if (this.state.showBack) { - backBtn = {this.setState({showBack: false});}}> - Back - ; - titleWidth = `calc(100% - ${navWidth}px)`; - } - - return ( - - -
- {backBtn} - - -
- {this.state.redirectTo && - - } - - - - -
-
-
-
- -
- ); - } -} - -export default App; diff --git a/src/js/App.jsx b/src/js/App.jsx new file mode 100644 index 0000000..bcdf24a --- /dev/null +++ b/src/js/App.jsx @@ -0,0 +1,192 @@ +import React from 'react'; +import { TitleBar } from 'react-desktop/windows'; +import { Theme as UWPThemeProvider, getTheme } from 'react-uwp/Theme'; +import NavigationView from 'react-uwp/NavigationView'; +import SplitViewCommand from 'react-uwp/SplitViewCommand'; +import { IconButton } from 'react-uwp'; +import PubSub from 'pubsub-js'; +import { + HashRouter as Router, + Redirect, + Link, + Route, +} from 'react-router-dom'; +import ToastHandler from './Components/toastHandler'; + +import UWPNoise from '../img/uwp-noise.png'; +import '../css/App.css'; +import Search from './Search'; +import Games from './games'; +import Import from './Import'; + +import Steam from './Steam'; + +// Using window.require so babel doesn't change the node require +const electron = window.require('electron'); +const { remote } = electron; + +// Log renderer errors +const log = window.require('electron-log'); +log.catchErrors({ showDialog: true }); + + +window.Steam = Steam; + +class App extends React.Component { + constructor(props) { + super(props); + + this.state = { isMaximized: false, showBack: false }; + this.toggleMaximize = this.toggleMaximize.bind(this); + + // Track windows snap calling maximize / unmaximize + const window = remote.getCurrentWindow(); + + window.on('maximize', () => { + this.setState({ isMaximized: true }); + }); + + window.on('unmaximize', () => { + this.setState({ isMaximized: false }); + }); + + PubSub.subscribe('showBack', (message, args) => { + this.setState({ showBack: args }); + }); + } + + close() { + remote.getCurrentWindow().close(); + } + + minimize() { + remote.getCurrentWindow().minimize(); + } + + toggleMaximize() { + const { isMaximized } = this.state; + const window = remote.getCurrentWindow(); + this.setState({ isMaximized: !isMaximized }); + if (!isMaximized) { + window.maximize(); + } else { + window.unmaximize(); + } + } + + handleNavRedirect(path) { + this.setState({ redirectTo: path }); + } + + render() { + const accentColor = remote.systemPreferences.getAccentColor(); + const navWidth = 48; + const { showBack, isMaximized, redirectTo } = this.state; + + const navigationTopNodes = [ this.handleNavRedirect('/')} />, this.handleNavRedirect('/import')} />]; + + let backBtn; + let titleWidth = '100%'; + if (showBack) { + backBtn = ( + { + this.setState({ showBack: false }); + }} + > + + Back + + + ); + titleWidth = `calc(100% - ${navWidth}px)`; + } + + return ( + + +
+ {backBtn} + + +
+ {redirectTo && } + + + + +
+
+
+
+ +
+ ); + } +} + +export default App; diff --git a/src/js/Components/Grid.jsx b/src/js/Components/Grid.jsx new file mode 100644 index 0000000..ca9a9f9 --- /dev/null +++ b/src/js/Components/Grid.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +class Grid extends React.Component { + constructor(props) { + super(props); + + const { zoom, platform } = this.props; + + this.zoom = zoom; + this.platform = platform; + } + + render() { + const { children, style } = this.props; + return ( +
+ {children} +
+ ); + } +} + +Grid.propTypes = { + children: PropTypes.node.isRequired, + platform: PropTypes.string, + zoom: PropTypes.number, + style: PropTypes.object, +}; + +export default Grid; diff --git a/src/js/Components/Import/ImportAllButton.jsx b/src/js/Components/Import/ImportAllButton.jsx new file mode 100644 index 0000000..c55d6a9 --- /dev/null +++ b/src/js/Components/Import/ImportAllButton.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import Button from 'react-uwp/Button'; +import PropTypes from 'prop-types'; + +class ImportAllButton extends React.Component { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + + handleClick() { + const { + onButtonClick, + games, + platform, + } = this.props; + + onButtonClick(games, platform); + } + + render() { + return ( + + ); + } +} + +ImportAllButton.propTypes = { + platform: PropTypes.object.isRequired, + games: PropTypes.array.isRequired, + onButtonClick: PropTypes.func, +}; + +ImportAllButton.defaultProps = { + onButtonClick: () => {}, +}; + +export default ImportAllButton; diff --git a/src/js/Components/Import/ImportList.jsx b/src/js/Components/Import/ImportList.jsx new file mode 100644 index 0000000..bf1a903 --- /dev/null +++ b/src/js/Components/Import/ImportList.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import ListView from 'react-uwp/ListView'; +import PropTypes from 'prop-types'; +import ImportListItem from './ImportListItem'; + +class ImportList extends React.Component { + constructor(props) { + super(props); + + const { + onImportClick, + games, + grids, + platform, + } = this.props; + + this.onImportClick = onImportClick; + this.games = games; + this.grids = grids; + this.platform = platform; + } + + render() { + const listStyle = { + background: 'none', + border: 0, + width: '100%', + marginBottom: 10, + clear: 'both', + }; + + const importList = ( + this.games.map((game, i) => { + let { progress } = game; + if (game.progress === undefined) { + progress = 0; + } + + let thumb; + if (this.grids[i].length > 0) { + thumb = this.grids[i][0].thumb; + } + + return ( + + ); + }) + ); + + return ( + + ); + } +} + +ImportList.propTypes = { + games: PropTypes.array.isRequired, + grids: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.bool, + ]).isRequired, + platform: PropTypes.object.isRequired, + onImportClick: PropTypes.func, +}; + +ImportList.defaultProps = { + onImportClick: () => {}, +}; +export default ImportList; diff --git a/src/js/Components/Import/ImportListItem.jsx b/src/js/Components/Import/ImportListItem.jsx new file mode 100644 index 0000000..1c217ac --- /dev/null +++ b/src/js/Components/Import/ImportListItem.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import Image from 'react-uwp/Image'; +import Button from 'react-uwp/Button'; +import ProgressBar from 'react-uwp/ProgressBar'; +import PropTypes from 'prop-types'; + +class ImportListItem extends React.Component { + constructor(props) { + super(props); + + const { + game, + platform, + } = this.props; + + this.game = game; + this.platform = platform; + this.handleClick = this.handleClick.bind(this); + } + + shouldComponentUpdate(nextProps) { + const { progress } = this.props; + return !(progress === nextProps.progress); + } + + handleClick() { + const { onImportClick } = this.props; + onImportClick(this.game, this.platform); + } + + render() { + const { progress, thumb } = this.props; + + let progressBar = <>; + if (progress && progress !== 1) { + progressBar = ; + } + + return ( +
+ + {this.game.name} + + {progressBar} +
+ ); + } +} + +ImportListItem.propTypes = { + platform: PropTypes.object.isRequired, + game: PropTypes.object.isRequired, + progress: PropTypes.number, + thumb: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + onImportClick: PropTypes.func, +}; + +ImportListItem.defaultProps = { + progress: 0, + thumb: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkqAcAAIUAgUW0RjgAAAAASUVORK5CYII=', + onImportClick: () => {}, +}; + +export default ImportListItem; diff --git a/src/js/Components/TopBlur.jsx b/src/js/Components/TopBlur.jsx new file mode 100644 index 0000000..f93d80e --- /dev/null +++ b/src/js/Components/TopBlur.jsx @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import UWPNoise from '../../img/uwp-noise.png'; + +const TopBlur = ({ additionalHeight }) => ( +
+); + +TopBlur.propTypes = { + additionalHeight: PropTypes.number, +}; + +TopBlur.defaultProps = { + additionalHeight: 0, +}; + +export default TopBlur; diff --git a/src/js/Components/gridImage.jsx b/src/js/Components/gridImage.jsx new file mode 100644 index 0000000..c63f7bd --- /dev/null +++ b/src/js/Components/gridImage.jsx @@ -0,0 +1,163 @@ +import React from 'react'; +import ProgressBar from 'react-uwp/ProgressBar'; +import { CSSTransitionGroup } from 'react-transition-group'; +import PropTypes from 'prop-types'; + +const ReactLazyLoad = require('react-lazyload').default; + +class GridImage extends React.Component { + constructor(props) { + super(props); + + const { zoom, onGridClick } = this.props; + + this.gridWidth = 300 * zoom; + this.gridHeight = 140 * zoom; + this.onGridClick = onGridClick; + this.handleClick = this.handleClick.bind(this); + } + + shouldComponentUpdate(nextProps) { + const { progress } = this.props; + return !(progress === nextProps.progress); + } + + handleClick() { + this.onGridClick(this.props); + } + + render() { + const { + progress, + image, + name, + author, + } = this.props; + const { theme } = this.context; + + let progressBar =
; + if (progress) { + progressBar = ( +
+ +
+ ); + } + + let lazyImage = ''; + if (image) { + lazyImage = ( + + + + + + ); + } + + return ( +
+ {lazyImage} + +
+ {name} +
+ +
+ {author && ( + + Grid by: + { ' ' } + {author} + + )} +
+ + {progressBar} +
+ ); + } +} + +GridImage.propTypes = { + name: PropTypes.string, + appid: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + index: PropTypes.number, + gameType: PropTypes.string, + gameId: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + platform: PropTypes.string, + author: PropTypes.string, + zoom: PropTypes.number, + progress: PropTypes.number, + image: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + onGridClick: PropTypes.func, +}; +GridImage.contextTypes = { theme: PropTypes.object }; + +export default GridImage; diff --git a/src/js/Components/spinner.jsx b/src/js/Components/spinner.jsx new file mode 100644 index 0000000..6377424 --- /dev/null +++ b/src/js/Components/spinner.jsx @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { ProgressCircle } from 'react-desktop/windows'; + +const Spinner = (props, context) => { + const { theme } = context; + const { text } = props; + + return ( +
+ +

{text}

+
+ ); +}; + +Spinner.propTypes = { + text: PropTypes.string, +}; + +Spinner.defaultProps = { + text: '', +}; + +Spinner.contextTypes = { theme: PropTypes.object }; +export default Spinner; diff --git a/src/js/Components/toastHandler.jsx b/src/js/Components/toastHandler.jsx new file mode 100644 index 0000000..ecc12c4 --- /dev/null +++ b/src/js/Components/toastHandler.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import PubSub from 'pubsub-js'; +import Toast from 'react-uwp/Toast'; +import Icon from 'react-uwp/Icon'; + +class ToastHandler extends React.Component { + constructor(props) { + super(props); + this.close = this.close; + this.state = { + toasts: [], + }; + } + + componentDidMount() { + PubSub.subscribe('toast', (message, args) => { + const { toasts } = this.state; + const toast = { toast: args, show: true }; + this.close(toast, 3000); + this.setState({ + toasts: toasts.concat(toast), + }); + }); + } + + close(toast, closeDelay) { + const self = this; + setTimeout(() => { + const toasts = self.state.toasts.slice(0); + toasts[toasts.indexOf(toast)].show = false; + self.setState({ toasts }); + }, closeDelay); + } + + render() { + const { toasts } = this.state; + return toasts.slice(0).map((x, i) => ( + {x.toast.logoNode}} + title={x.toast.title} + showCloseIcon + > + {x.toast.contents} + + )); + } +} + +export default ToastHandler; diff --git a/src/js/Grid.js b/src/js/Grid.js deleted file mode 100644 index beeb712..0000000 --- a/src/js/Grid.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -class Grid extends React.Component { - constructor(props) { - super(props); - - this.zoom = this.props.zoom; - this.platform = this.props.platform; - } - - render() { - return ( -
- {this.props.children} -
- ); - } -} - -Grid.propTypes = { - children: PropTypes.node.isRequired, - platform: PropTypes.string, - zoom: PropTypes.number, - style: PropTypes.object -}; - -export default Grid; \ No newline at end of file diff --git a/src/js/Import.js b/src/js/Import.js deleted file mode 100644 index b5de70f..0000000 --- a/src/js/Import.js +++ /dev/null @@ -1,272 +0,0 @@ -const Store = window.require('electron-store'); -const SGDB = window.require('steamgriddb'); -const metrohash64 = window.require('metrohash').metrohash64; -const log = window.require('electron-log'); -import PubSub from 'pubsub-js'; -import React from 'react'; -import settle from 'promise-settle'; -import PropTypes from 'prop-types'; -import Image from 'react-uwp/Image'; -import ImportList from './ImportList'; -import ImportAllButton from './ImportAllButton.js'; -import Spinner from './spinner.js'; -import TopBlur from './TopBlur'; -import Steam from './Steam'; -import platformModules from './importers'; - -class Import extends React.Component { - constructor(props) { - super(props); - - this.addGame = this.addGame.bind(this); - this.addGames = this.addGames.bind(this); - - this.store = new Store(); - - this.platforms = Object.keys(platformModules).map((key) => ({ - id: platformModules[key].id, - name: platformModules[key].name, - class: platformModules[key].default, - error: false - })); - - this.SGDB = new SGDB('b971a6f5f280490ab62c0ee7d0fd1d16'); - - this.state = { - isLoaded: false, - games: {} - }; - } - - componentDidMount() { - Promise.all(this.platforms.map((platform) => platform.class.isInstalled())) - .then((values) => { - for (let i = 0; i < values.length; i++) { - this.platforms[i].installed = values[i]; - } - - // Generate array of getGames() promises if installed - const getGamesPromises = this.platforms.map((platform) => { - if (platform.installed) { - return platform.class.getGames(); - } else { - return false; - } - }); - - settle(getGamesPromises).then((results) => { - const games = {}; - const gridsPromises = []; - - results.forEach((result, index) => { - if (result.isFulfilled() && result.value() !== false) { - games[this.platforms[index].id] = result.value(); - - log.info(`Import: ${result.value().length} games found for ${this.platforms[index].id}`); - - if (result.value().length > 0) { - const ids = result.value().map((x) => encodeURIComponent(x.id)); // Comma separated list of IDs for use with SGDB API - const platform = result.value()[0].platform; - - log.info(ids.join(',')); - - // Get grids for each game - const getGrids = this.SGDB.getGrids({type: platform, id: ids.join(',')}).then((res) => { - let formatted; - // if only single id then return first grid - if (result.value().length === 1) { - if (res.length > 0) { - formatted = [res[0]]; - } - } else { - // if multiple ids treat each object as a request - formatted = res.map((x) => { - if (x.success) { - return x.data; - } else { - return false; - } - }); - } - return formatted; - }).catch(() => { - // show an error toast - }); - gridsPromises.push(getGrids); - } else { - gridsPromises.push(false); - } - } else if (result.isRejected()) { - // getGames() rejected - this.platforms[index].error = true; - this.platforms[index].errorReason = result.reason(); - games[this.platforms[index].id] = []; - gridsPromises.push(false); - log.info(`Import: ${this.platforms[index].id} rejected ${result.reason()}`); - } else { - // not installed - games[this.platforms[index].id] = false; - gridsPromises.push(false); - log.info(`Import: ${this.platforms[index].id} not installed`); - } - }); - - Promise.all(gridsPromises).then((values) => { - this.setState({ - isLoaded: true, - games: games, - grids: values - }); - }); - }); - }); - } - - platformGamesSave(games) { - let gamesStorage = this.store.get('games'); - if (!gamesStorage) { - gamesStorage = {}; - } - - games.forEach((game) => { - gamesStorage[metrohash64(game.exe+(typeof game.params !== 'undefined' ? game.params : ''))] = game; - }); - this.store.set('games', gamesStorage); - } - - platformGameSave(game) { - this.store.set(`games.${metrohash64(game.exe+(typeof game.params !== 'undefined' ? game.params : ''))}`, game); - } - - platformGameRemove(game) { - this.store.delete(`games.${metrohash64(game.exe+game.params)}`); - } - - addGames(games, grids, platform) { - this.platformGamesSave(games); - - // Add shortcuts with platform name as tag - Steam.addShortcuts(games.map((game) => { - game.tags = [platform.name]; - return game; - })); - - const addGridPromises = []; - games.forEach((game, i) => { - let image = null; - if (games.length > 1 && grids[i].length === 1 && grids[i][0].success !== false) { - image = grids[i][0].url; - } else if (games.length === 1 && grids) { - image = grids[0].url; - } - if (image) { - const gamesClone = Object.assign({}, this.state.games); - const addGrid = Steam.addGrid(Steam.generateAppId(game.exe, game.name), image, (progress) => { - gamesClone[platform.id][gamesClone[platform.id].indexOf(game)].progress = progress; - this.setState({gamesClone}); - }); - - addGridPromises.push(addGrid); - } - }); - - Promise.all(addGridPromises).then(() => { - PubSub.publish('toast', {logoNode: 'ImportAll', title: 'Successfully Imported!', contents: ( -

{games.length} games imported from {platform.name}

- )}); - }); - } - - addGame(game, image, platform) { - this.platformGameSave(game); - Steam.addShortcuts([{ - name: game.name, - exe: game.exe, - startIn: game.startIn, - params: game.params, - tags: [platform.name], - icon: game.icon - }]); - if (image) { - const gamesClone = Object.assign({}, this.state.games); - Steam.addGrid(Steam.generateAppId(game.exe, game.name), image, (progress) => { - gamesClone[platform.id][gamesClone[platform.id].indexOf(game)].progress = progress; - this.setState({gamesClone}); - }).then((dest) => { - PubSub.publish('toast', {logoNode: 'Import', title: `Successfully Imported: ${game.name}`, contents: ( - - )}); - }).catch((err) => { - PubSub.publish('toast', {logoNode: 'Error', title: `Failed to import: ${game.name}`, contents: ( -

{err.message}

- )}); - }); - } - } - - render() { - const {isLoaded, games, grids} = this.state; - - if (!isLoaded) { - return (); - } - - // if no launcher installed - let noLaunchers = false; - if (Object.values(games).every((x) => x === false)) { - noLaunchers = -
-

Looks like you have no launchers installed.

-

The following launchers are supported: {this.platforms.map((x) => x.name).join(', ')}

-
; - } - - return ( - <> - -
- {noLaunchers ? ( - noLaunchers - ) : ( - Object.keys(games).map((platform, i) => { - if (this.platforms[i].installed) { - if (!this.platforms[i].error) { - return ( -
-
{this.platforms[i].name}
- - -
- ); - } else { - return ( -
-
{this.platforms[i].name}
-

Error importing: {this.platforms[i].errorReason.message}

-
- ); - } - } - }) - )} -
- - ); - } -} - -Import.contextTypes = { theme: PropTypes.object }; -export default Import; diff --git a/src/js/Import.jsx b/src/js/Import.jsx new file mode 100644 index 0000000..ef403fd --- /dev/null +++ b/src/js/Import.jsx @@ -0,0 +1,219 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import PubSub from 'pubsub-js'; +import ImportList from './Components/Import/ImportList'; +import ImportAllButton from './Components/Import/ImportAllButton'; +import Spinner from './Components/spinner'; +import TopBlur from './Components/TopBlur'; +import Steam from './Steam'; +import platformModules from './importers'; + +const Store = window.require('electron-store'); +const SGDB = window.require('steamgriddb'); +const { metrohash64 } = window.require('metrohash'); +const log = window.require('electron-log'); + +class Import extends React.Component { + constructor(props) { + super(props); + + this.addGame = this.addGame.bind(this); + this.addGames = this.addGames.bind(this); + + this.store = new Store(); + + this.platforms = Object.keys(platformModules).map((key) => ({ + id: platformModules[key].id, + name: platformModules[key].name, + class: platformModules[key].default, + games: [], + grids: [], + installed: false, + error: false, + })); + + this.SGDB = new SGDB('b971a6f5f280490ab62c0ee7d0fd1d16'); + + this.state = { + isLoaded: false, + loadingText: '', + installedPlatforms: [], + }; + } + + componentDidMount() { + Promise.all(this.platforms.map((platform) => platform.class.isInstalled())) + .then((values) => { + // Set .installed + this.platforms.forEach((platform, index) => { + platform.installed = values[index]; + }); + + const installedPlatforms = this.platforms.filter((platform) => (platform.installed)); + + // Do .getGames() in sequential order + const getGames = installedPlatforms + .reduce((promise, platform) => promise.then(() => { + this.setState({ loadingText: `Grabbing games from ${platform.name}...` }); + return platform.class.getGames().then((games) => { + // Populate games array + platform.games = games; + }); + }).catch((err) => { + platform.error = true; + log.info(`Import: ${platform.id} rejected ${err}`); + }), Promise.resolve()); + + getGames.then(() => { + this.setState({ loadingText: 'Getting images...' }); + + const gridsPromises = []; + 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) => { + let formatted; + // if only single id then return first grid + if (ids.length === 1) { + if (res.length > 0) { + formatted = [[res[0]]]; + } + } else { + // if multiple ids treat each object as a request + formatted = res.map((x) => { + if (x.success) { + return x.data; + } + return false; + }); + } + platform.grids = formatted; + }).catch(() => { + // show an error toast + }); + gridsPromises.push(getGrids); + }); + + // Update state after we got the grids + Promise.all(gridsPromises).then(() => { + this.setState({ + isLoaded: true, + installedPlatforms, + }); + }); + }).catch((err) => { + log.info(`Import: ${err}`); + }); + }); + } + + saveImportedGames(games) { + const gamesStorage = this.store.get('games', {}); + games.forEach((game) => { + gamesStorage[metrohash64(game.exe + (game.params !== 'undefined' ? game.params : ''))] = game; + }); + this.store.set('games', gamesStorage); + } + + addGames(games, platform) { + this.saveImportedGames(games); + + const shortcuts = games.map((game) => ({ + name: game.name, + exe: game.exe, + startIn: game.startIn, + params: game.params, + tags: [platform.name], + icon: game.icon, + })); + + Steam.addShortcuts(shortcuts).then(() => { + Steam.addCategory(games, platform.name).then(() => { + PubSub.publish('toast', { + logoNode: 'ImportAll', + title: 'Successfully Imported!', + contents: ( +

+ {games.length} + { ' ' } + games imported from + { ' ' } + {platform.name} +

+ ), + }); + }).catch((err) => { + if (err.type === 'OpenError') { + PubSub.publish('toast', { + logoNode: 'Error', + title: 'Error Importing', + contents: ( +

+ Cannot import while Steam is running. +
+ Close Steam and try again. +

+ ), + }); + } + }); + }); + } + + addGame(game, platform) { + return this.addGames([game], platform); + } + + render() { + const { isLoaded, loadingText, installedPlatforms } = this.state; + const { theme } = this.context; + + if (!isLoaded) { + return (); + } + + return ( + <> + +
+ { + installedPlatforms.map((platform) => { + if (!platform.error) { + return ( +
+
{platform.name}
+ + +
+ ); + } + return <>; + }) + } +
+ + ); + } +} + +Import.contextTypes = { theme: PropTypes.object }; +export default Import; diff --git a/src/js/ImportAllButton.js b/src/js/ImportAllButton.js deleted file mode 100644 index 519c0cf..0000000 --- a/src/js/ImportAllButton.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import Button from 'react-uwp/Button'; -import PropTypes from 'prop-types'; - -class ImportAllButton extends React.Component { - constructor(props) { - super(props); - - this.handleClick = this.handleClick.bind(this); - } - - handleClick() { - this.props.onButtonClick(this.props.games, this.props.grids, this.props.platform); - } - - render() { - return ( - - ); - } -} - -ImportAllButton.propTypes = { - platform: PropTypes.object.isRequired, - games: PropTypes.array.isRequired, - grids: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.bool - ]).isRequired, - onButtonClick: PropTypes.func -}; - -export default ImportAllButton; \ No newline at end of file diff --git a/src/js/ImportList.js b/src/js/ImportList.js deleted file mode 100644 index c08c302..0000000 --- a/src/js/ImportList.js +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import ListView from 'react-uwp/ListView'; -import PropTypes from 'prop-types'; -import ImportListItem from './ImportListItem'; - -class ImportList extends React.Component { - constructor(props) { - super(props); - - this.games = this.props.games; - this.grids = this.props.grids; - this.platform = this.props.platform; - this.onImportClick = this.props.onImportClick; - } - - render() { - const listStyle = { - background: 'none', - border: 0, - width: '100%', - marginBottom: 10, - clear: 'both' - }; - - const importList = ( - this.games.map((game, i) => { - let thumb = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkqAcAAIUAgUW0RjgAAAAASUVORK5CYII='; - let image = null; - if (this.grids) { - if (this.games.length > 1 && this.grids[i].length === 1 && this.grids[i][0].success !== false) { - thumb = this.grids[i][0].thumb; - image = this.grids[i][0].url; - } else if (this.games.length === 1 && this.grids) { - thumb = this.grids[0].thumb; - image = this.grids[0].url; - } - } - - let progress = game.progress; - if (typeof game.progress == 'undefined') { - progress = 0; - } - - return ( - - ); - }) - ); - - return ( - - ); - } -} - -ImportList.propTypes = { - games: PropTypes.array.isRequired, - grids: PropTypes.oneOfType([ - PropTypes.array, - PropTypes.bool - ]).isRequired, - platform: PropTypes.object.isRequired, - onImportClick: PropTypes.func -}; - -export default ImportList; diff --git a/src/js/ImportListItem.js b/src/js/ImportListItem.js deleted file mode 100644 index 3f54592..0000000 --- a/src/js/ImportListItem.js +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import Image from 'react-uwp/Image'; -import Button from 'react-uwp/Button'; -import ProgressBar from 'react-uwp/ProgressBar'; -import PropTypes from 'prop-types'; - -class ImportListItem extends React.Component { - constructor(props) { - super(props); - this.image = this.props.image; - this.thumb = this.props.thumb; - this.game = this.props.game; - this.platform = this.props.platform; - this.handleClick = this.handleClick.bind(this); - } - - shouldComponentUpdate(nextProps) { - return !(this.props.progress === nextProps.progress); - } - - handleClick() { - this.props.onImportClick(this.game, this.image, this.platform); - } - - render() { - let progressBar =
; - if (this.props.progress && this.props.progress !== 1) { - progressBar = ; - } - - return ( -
- - {this.game.name} - - {progressBar} -
- ); - } -} - -ImportListItem.propTypes = { - platform: PropTypes.object.isRequired, - game: PropTypes.object.isRequired, - progress: PropTypes.number, - image: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool - ]), - thumb: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool - ]), - onImportClick: PropTypes.func -}; - -export default ImportListItem; \ No newline at end of file diff --git a/src/js/Search.js b/src/js/Search.js deleted file mode 100644 index 2a1da93..0000000 --- a/src/js/Search.js +++ /dev/null @@ -1,230 +0,0 @@ -import Spinner from './spinner.js'; -import GridImage from './gridImage.js'; -import {Redirect} from 'react-router-dom'; -import Steam from './Steam.js'; -import React from 'react'; -import Image from 'react-uwp/Image'; -import Grid from './Grid'; -import TopBlur from './TopBlur'; -import queryString from 'query-string'; -import PropTypes from 'prop-types'; -import PubSub from 'pubsub-js'; -import {officialList} from './importers'; -const SGDB = window.require('steamgriddb'); -const Store = window.require('electron-store'); - -class Search extends React.Component { - constructor(props) { - super(props); - - this.applyGrid = this.applyGrid.bind(this); - this.zoom = 1; - this.store = new Store(); - - const qs = this.props.location && queryString.parse(this.props.location.search); - this.game = qs.game; - this.query = qs.game; - this.appid = qs.appid; - this.gameType = qs.type; - this.platform = qs.platform; - this.gameId = qs.gameId; - - this.state = { - error: null, - apiError: false, - isLoaded: false, - isHover: false, - isDownloading: false, - imageDownloaded: false, - items: [] - }; - - this.setImageDownloaded = this.setImageDownloaded.bind(this); - this.setIsDownloading = this.setIsDownloading.bind(this); - this.getIsDownloading = this.getIsDownloading.bind(this); - - PubSub.publish('showBack', true); - } - - componentDidMount() { - if (this.state.items.length <= 0) { - this.searchGrids(); - } - } - - // @todo This should be it's own class so we can use it during one-click downloads - searchGrids() { - const client = new SGDB('b971a6f5f280490ab62c0ee7d0fd1d16'); - - if (this.gameType === 'game') { - const defaultGridImage = Steam.getDefaultGridImage(this.appid); - const items = [{ - url: defaultGridImage, - thumb: defaultGridImage, - style: 'default', - title: this.query, - author: { - name: null - } - }]; - client.getGridsBySteamAppId(this.appid) - .then((res) => { - this.setState({ - isLoaded: true, - items: [...items, ...res] - }); - }) - .catch((err) => { - if (err.response.statusCode === 404) { - // Game not found is fine - this.setState({ - isLoaded: true, - items: items - }); - } else { - // Any other error is baad - this.setState({ - apiError: true - }); - } - }); - } - - if (this.gameType === 'shortcut' && officialList.includes(this.platform)) { - client.getGrids({id: this.gameId, type: this.platform}) - .then((items) => { - this.setState({ - isLoaded: true, - items: items - }); - }) - .catch(() => { - this.setState({ - apiError: true - }); - }); - } else if (this.gameType === 'shortcut' && !officialList.includes(this.platform)) { - client.searchGame(this.query) - .then((res) => { - client.getGridsById(res[0].id) - .then((items) => { - this.setState({ - isLoaded: true, - items: items - }); - }); - }).catch(() => { - this.setState({ - apiError: true - }); - }); - } - } - - applyGrid(props) { - if (this.getIsDownloading()) { - return; - } - - this.setIsDownloading(true); - const itemsClone = Object.assign({}, this.state.items); - Steam.addGrid(props.appid, props.image, (progress) => { - this.setState({downloadProgress: progress}); - itemsClone[props.index].progress = progress; - this.setState({itemsClone}); - }).then((dest) => { - this.setImageDownloaded(props.appid, props.name, dest); - }).catch(() => { - this.setIsDownloading(false); - }); - } - - setIsDownloading(isDownloading) { - this.setState({isDownloading: isDownloading}); - } - - getIsDownloading() { - return this.state.isDownloading; - } - - setImageDownloaded(appid, game, image) { - this.setState({ - imageDownloaded: { - appid: appid, - game: game, - image: image - }, - isDownloading: false - }); - } - - render() { - const {isLoaded, items} = this.state; - - if (this.state.imageDownloaded) { - const url = `/?scrollto=${this.state.imageDownloaded.appid}`; - - // Show toast - PubSub.publish('toast', {logoNode: 'Download', title: `Success: ${this.state.imageDownloaded.game}`, contents: ( - - )}); - - return ( -
- -
- ); - } - - if (!isLoaded) { - return (); - } - - return ( - <> - -
- {this.state.apiError ? ( -
-
- Error trying to use the SteamGridDB API. -
-
- ) : ( - - {items.map((item, i) => { - let progress = item.progress; - if (typeof item.progress == 'undefined') { - progress = 0; - } - return ( - - ); - })} - - )} -
- - ); - } -} - -Search.propTypes = { - location: PropTypes.object -}; -Search.contextTypes = { theme: PropTypes.object }; -export default Search; diff --git a/src/js/Search.jsx b/src/js/Search.jsx new file mode 100644 index 0000000..e20beef --- /dev/null +++ b/src/js/Search.jsx @@ -0,0 +1,244 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import Image from 'react-uwp/Image'; +import queryString from 'query-string'; +import PropTypes from 'prop-types'; +import PubSub from 'pubsub-js'; +import Grid from './Components/Grid'; +import TopBlur from './Components/TopBlur'; +import Spinner from './Components/spinner'; +import GridImage from './Components/gridImage'; +import { officialList } from './importers'; +import Steam from './Steam'; + +const SGDB = window.require('steamgriddb'); +const Store = window.require('electron-store'); + +class Search extends React.Component { + constructor(props) { + super(props); + const { location } = this.props; + + this.applyGrid = this.applyGrid.bind(this); + this.zoom = 1; + this.store = new Store(); + + const qs = location && queryString.parse(location.search); + this.game = qs.game; + this.query = qs.game; + this.appid = qs.appid; + this.gameType = qs.type; + this.platform = qs.platform; + this.gameId = qs.gameId; + + this.state = { + error: null, + apiError: false, + isLoaded: false, + isHover: false, + isDownloading: false, + imageDownloaded: false, + items: [], + }; + + this.setImageDownloaded = this.setImageDownloaded.bind(this); + this.setIsDownloading = this.setIsDownloading.bind(this); + this.getIsDownloading = this.getIsDownloading.bind(this); + + PubSub.publish('showBack', true); + } + + componentDidMount() { + const { items } = this.state; + if (items.length <= 0) { + this.searchGrids(); + } + } + + setIsDownloading(isDownloading) { + this.setState({ isDownloading }); + } + + getIsDownloading() { + const { isDownloading } = this.state; + return isDownloading; + } + + setImageDownloaded(appid, game, image) { + this.setState({ + imageDownloaded: { appid, game, image }, + isDownloading: false, + }); + } + + applyGrid(props) { + if (this.getIsDownloading()) { + return; + } + + this.setIsDownloading(true); + const itemsClone = { ...this.state.items }; + Steam.addGrid(props.appid, props.image, (progress) => { + this.setState({ downloadProgress: progress }); + itemsClone[props.index].progress = progress; + this.setState({ itemsClone }); + }).then((dest) => { + this.setImageDownloaded(props.appid, props.name, dest); + }).catch(() => { + this.setIsDownloading(false); + }); + } + + searchGrids() { + const client = new SGDB('b971a6f5f280490ab62c0ee7d0fd1d16'); + + if (this.gameType === 'game') { + const defaultGridImage = Steam.getDefaultGridImage(this.appid); + const items = [{ + url: defaultGridImage, + thumb: defaultGridImage, + style: 'default', + title: this.query, + author: { + name: null, + }, + }]; + client.getGridsBySteamAppId(this.appid) + .then((res) => { + this.setState({ + isLoaded: true, + items: [...items, ...res], + }); + }) + .catch((err) => { + if (err.response.statusCode === 404) { + // Game not found is fine + this.setState({ + isLoaded: true, + items, + }); + } else { + // Any other error is baad + this.setState({ apiError: true }); + } + }); + } + + if (this.gameType === 'shortcut' && officialList.includes(this.platform)) { + client.getGrids({ id: this.gameId, type: this.platform }) + .then((items) => { + this.setState({ + isLoaded: true, + items, + }); + }) + .catch(() => { + this.setState({ + apiError: true, + }); + }); + } else if (this.gameType === 'shortcut' && !officialList.includes(this.platform)) { + client.searchGame(this.query) + .then((res) => { + client.getGridsById(res[0].id) + .then((items) => { + this.setState({ + isLoaded: true, + items, + }); + }); + }).catch(() => { + this.setState({ apiError: true }); + }); + } + } + + render() { + const { + isLoaded, + items, + apiError, + imageDownloaded, + } = this.state; + const { theme } = this.context; + + if (imageDownloaded) { + const url = `/?scrollto=${imageDownloaded.appid}`; + + // Show toast + PubSub.publish('toast', { + logoNode: 'Download', + title: `Success: ${imageDownloaded.game}`, + contents: ( + + ), + }); + + return ( +
+ +
+ ); + } + + if (!isLoaded) { + return (); + } + + return ( + <> + +
+ {apiError ? ( +
+
+ Error trying to use the SteamGridDB API. +
+
+ ) : ( + + {items.map((item, i) => { + let { progress } = item; + if (typeof item.progress === 'undefined') { + progress = 0; + } + return ( + + ); + })} + + )} +
+ + ); + } +} + +Search.propTypes = { + location: PropTypes.object +}; +Search.contextTypes = { theme: PropTypes.object }; +export default Search; diff --git a/src/js/Steam.js b/src/js/Steam.js index aaba6e3..7fb5cd1 100644 --- a/src/js/Steam.js +++ b/src/js/Steam.js @@ -1,379 +1,431 @@ +import SteamID from 'steamid'; +import { crc32 } from 'crc'; + const Registry = window.require('winreg'); const Store = window.require('electron-store'); const fs = window.require('fs'); -const {join} = window.require('path'); +const { join } = window.require('path'); const VDF = window.require('@node-steam/vdf'); const shortcut = window.require('steam-shortcut-editor'); const https = window.require('https'); const Stream = window.require('stream').Transform; -const metrohash64 = window.require('metrohash').metrohash64; +const { metrohash64 } = window.require('metrohash'); const log = window.require('electron-log'); -import SteamID from 'steamid'; -import {crc32} from 'crc'; +const Categories = window.require('steam-categories'); class Steam { - constructor() { - this.steamPath = null; - this.loggedInUser = null; - this.currentUserGridPath = null; - } + constructor() { + this.steamPath = null; + this.loggedInUser = null; + this.currentUserGridPath = null; + } + + static getSteamPath() { + return new Promise((resolve, reject) => { + if (this.steamPath) { + return resolve(this.steamPath); + } + + const key = new Registry({ + hive: Registry.HKCU, + key: '\\Software\\Valve\\Steam', + }); + + key.values((err, items) => { + let steamPath = false; + + items.forEach((item) => { + if (item.name === 'SteamPath') { + steamPath = item.value; + } + }); - static getSteamPath() { - return new Promise((resolve, reject) => { - if (this.steamPath) { - return resolve(this.steamPath); - } + if (steamPath) { + this.steamPath = steamPath; + log.info(`Got Steam path: ${steamPath}`); + return resolve(steamPath); + } - const key = new Registry({ - hive: Registry.HKCU, - key: '\\Software\\Valve\\Steam' + return reject(new Error('Could not find Steam path.')); + }); + + return false; + }); + } + + static getCurrentUserGridPath() { + return new Promise((resolve) => { + if (this.currentUserGridPath) { + return resolve(this.currentUserGridPath); + } + this.getSteamPath().then((steamPath) => { + this.getLoggedInUser().then((user) => { + const gridPath = join(steamPath, 'userdata', String(user), 'config', 'grid'); + if (!fs.existsSync(gridPath)) { + fs.mkdirSync(gridPath); + } + this.currentUserGridPath = gridPath; + resolve(gridPath); + }); + }); + return false; + }); + } + + static getSteamGames() { + return new Promise((resolve) => { + this.getSteamPath().then((steamPath) => { + this.getCurrentUserGridPath().then((userdataPath) => { + const parsedLibFolders = VDF.parse(fs.readFileSync(join(steamPath, 'steamapps', 'libraryfolders.vdf'), 'utf-8')); + const games = []; + const libraries = []; + + // Add Steam install dir + libraries.push(steamPath); + + // Add library folders from libraryfolders.vdf + Object.keys(parsedLibFolders.LibraryFolders).forEach((key) => { + const library = parsedLibFolders.LibraryFolders[key]; + if (!Number.isNaN(parseInt(key, 10))) { + libraries.push(library); + } + }); + + log.info(`Found ${libraries.length} Steam libraries`); + + libraries.forEach((library) => { + const appsPath = join(library, 'steamapps'); + const files = fs.readdirSync(appsPath); + files.forEach((file) => { + const ext = file.split('.').pop(); + + if (ext === 'acf') { + const filePath = join(appsPath, file); + const data = fs.readFileSync(filePath, 'utf-8'); + try { + const gameData = VDF.parse(data); + if (gameData.AppState.appid === 228980) { + return; + } + + let image = this.getCustomGridImage(userdataPath, gameData.AppState.appid); + + if (!image) { + image = this.getDefaultGridImage(gameData.AppState.appid); + } + + games.push({ + appid: gameData.AppState.appid, + name: gameData.AppState.name, + imageURI: image, + type: 'game', + image, + }); + } catch (err) { + log.warn(`Error while parsing ${file}: ${err}`); + } + } }); + }); + log.info(`Fetched ${games.length} Steam games`); - key.values((err, items) => { - let steamPath = false; - - items.forEach((item) => { - if (item.name === 'SteamPath') { - steamPath = item.value; - } - }); + resolve(games); + }); + }); + }); + } + + static getNonSteamGames() { + return new Promise((resolve) => { + this.getSteamPath().then((steamPath) => { + this.getLoggedInUser().then((user) => { + const store = new Store(); + const userdataPath = join(steamPath, 'userdata', String(user)); + const userdataGridPath = join(userdataPath, 'config', 'grid'); + const shortcutPath = join(userdataPath, 'config', 'shortcuts.vdf'); + shortcut.parseFile(shortcutPath, (err, items) => { + const games = {}; + + if (!items) { + return resolve([]); + } - if (steamPath) { - this.steamPath = steamPath; - log.info(`Got Steam path: ${steamPath}`); - resolve(steamPath); - } else { - reject(new Error('Could not find Steam path.')); + items.shortcuts.forEach((item) => { + const appName = item.appname || item.AppName; + const exe = item.exe || item.Exe; + const appid = this.generateAppId(exe, appName); + const image = this.getCustomGridImage(userdataGridPath, appid); + let imageURI = false; + if (image) { + imageURI = `file://${image.replace(/ /g, '%20')}`; + } + + const configId = metrohash64(exe + item.LaunchOptions); + if (store.has(`games.${configId}`)) { + const storedGame = store.get(`games.${configId}`); + if (typeof games[storedGame.platform] === 'undefined') { + games[storedGame.platform] = []; } - }); - }); - } - static getCurrentUserGridPath() { - return new Promise((resolve) => { - if (this.currentUserGridPath) { - return resolve(this.currentUserGridPath); - } - this.getSteamPath().then((steamPath) => { - this.getLoggedInUser().then((user) => { - const gridPath = join(steamPath, 'userdata', String(user), 'config', 'grid'); - if (!fs.existsSync(gridPath)){ - fs.mkdirSync(gridPath); - } - this.currentUserGridPath = gridPath; - resolve(gridPath); + games[storedGame.platform].push({ + gameId: storedGame.id, + name: appName, + platform: storedGame.platform, + type: 'shortcut', + appid, + image, + imageURI, }); - }); - }); - } + } else { + if (!games.other) { + games.other = []; + } - static getSteamGames() { - return new Promise((resolve) => { - this.getSteamPath().then((steamPath) => { - this.getCurrentUserGridPath().then((userdataPath) => { - const parsedLibFolders = VDF.parse(fs.readFileSync(join(steamPath, 'steamapps', 'libraryfolders.vdf'), 'utf-8')); - const games = []; - const libraries = []; - - // Add Steam install dir - libraries.push(steamPath); - - // Add library folders from libraryfolders.vdf - Object.keys(parsedLibFolders.LibraryFolders).forEach((key) => { - const library = parsedLibFolders.LibraryFolders[key]; - if (!isNaN(key)) { - libraries.push(library); - } - }); - - log.info(`Found ${libraries.length} Steam libraries`); - - libraries.forEach((library) => { - const appsPath = join(library, 'steamapps'); - const files = fs.readdirSync(appsPath); - files.forEach((file) => { - const ext = file.split('.').pop(); - - if (ext === 'acf') { - const filePath = join(appsPath, file); - const data = fs.readFileSync(filePath, 'utf-8'); - try { - const gameData = VDF.parse(data); - if (gameData.AppState.appid === 228980) { - return; - } - - let image = this.getCustomGridImage(userdataPath, gameData.AppState.appid); - - if (!image) { - image = this.getDefaultGridImage(gameData.AppState.appid); - } - - games.push({ - appid: gameData.AppState.appid, - name: gameData.AppState.name, - image: image, - imageURI: image, - type: 'game' - }); - } catch(err) { - log.warn(`Error while parsing ${file}: ${err}`); - return; - } - } - }); - }); - log.info(`Fetched ${games.length} Steam games`); - - resolve(games); + games.other.push({ + gameId: null, + name: appName, + platform: 'other', + type: 'shortcut', + appid, + image, + imageURI, }); + } }); + return resolve(games); + }); }); - } - - static getNonSteamGames() { - return new Promise((resolve) => { - this.getSteamPath().then((steamPath) => { - this.getLoggedInUser().then((user) => { - const store = new Store(); - const userdataPath = join(steamPath, 'userdata', String(user)); - const userdataGridPath = join(userdataPath, 'config', 'grid'); - const shortcutPath = join(userdataPath, 'config', 'shortcuts.vdf'); - shortcut.parseFile(shortcutPath, (err, items) => { - const games = {}; - - if (!items) { - return resolve([]); - } - - items.shortcuts.forEach((item) => { - const appName = item.appname || item.AppName; - const exe = item.exe || item.Exe; - const appid = this.generateAppId(exe, appName); - const image = this.getCustomGridImage(userdataGridPath, appid); - let imageURI = false; - if (image) { - imageURI = `file://${image.replace(/ /g, '%20')}`; - } - - const configId = metrohash64(exe+item.LaunchOptions); - if (store.has(`games.${configId}`)) { - const storedGame = store.get(`games.${configId}`); - if (typeof games[storedGame.platform] == 'undefined') { - games[storedGame.platform] = []; - } - - games[storedGame.platform].push({ - gameId: storedGame.id, - appid: appid, - name: appName, - platform: storedGame.platform, - image: image, - imageURI: imageURI, - type: 'shortcut' - }); - } else { - if (!games['other']) { - games['other'] = []; - } - - games['other'].push({ - gameId: null, - appid: appid, - name: appName, - platform: 'other', - image: image, - imageURI: imageURI, - type: 'shortcut' - }); - } - }); - resolve(games); - }); - }); - }); + }); + }); + } + + /* eslint-disable no-bitwise, no-mixed-operators */ + static generateAppId(exe, name) { + const key = exe + name; + const top = BigInt(crc32(key)) | BigInt(0x80000000); + return String((BigInt(top) << BigInt(32) | BigInt(0x02000000))); + } + + // Appid for new library. + // Thanks to https://gist.github.com/stormyninja/6295d5e6c1c9c19ab0ce46d546e6d0b1 & https://gitlab.com/avalonparton/grid-beautification + static generateNewAppId(exe, name) { + const key = exe + name; + const top = BigInt(crc32(key)) | BigInt(0x80000000); + const shift = (BigInt(top) << BigInt(32) | BigInt(0x02000000)) >> BigInt(32); + return parseInt(shift, 10); + } + /* eslint-enable no-bitwise, no-mixed-operators */ + + static getLoggedInUser() { + return new Promise((resolve) => { + if (this.loggedInUser) { + return resolve(this.loggedInUser); + } + + this.getSteamPath().then((steamPath) => { + const loginusersPath = join(steamPath, 'config', 'loginusers.vdf'); + const data = fs.readFileSync(loginusersPath, 'utf-8'); + const loginusersData = VDF.parse(data); + + Object.keys(loginusersData.users).every((user) => { + if (loginusersData.users[user].mostrecent) { + const { accountid } = (new SteamID(user)); + this.loggedInUser = accountid; + log.info(`Got Steam user: ${accountid}`); + resolve(accountid); + return true; + } + return false; }); + }); + + return false; + }); + } + + static getDefaultGridImage(appid) { + return `https://steamcdn-a.akamaihd.net/steam/apps/${appid}/header.jpg`; + } + + static getCustomGridImage(userdataGridPath, appid) { + const fileTypes = ['jpg', 'jpeg', 'png', 'tga']; + const basePath = join(userdataGridPath, String(appid)); + let image = false; + + fileTypes.some((ext) => { + const path = `${basePath}.${ext}`; + + if (fs.existsSync(path)) { + image = path; + return true; + } + return false; + }); + + return image; + } + + static deleteCustomGridImage(userdataGridPath, appid) { + const imagePath = this.getCustomGridImage(userdataGridPath, appid); + if (imagePath) { + fs.unlinkSync(imagePath); } - - static generateAppId(exe, name) { - const key = exe + name; - const top = BigInt(crc32(key)) | BigInt(0x80000000); - return String((BigInt(top) << BigInt(32) | BigInt(0x02000000))); - } - - static getLoggedInUser() { - return new Promise((resolve) => { - if (this.loggedInUser) { - return resolve(this.loggedInUser); - } - - this.getSteamPath().then((steamPath) => { - const loginusersPath = join(steamPath, 'config', 'loginusers.vdf'); - const data = fs.readFileSync(loginusersPath, 'utf-8'); - const loginusersData = VDF.parse(data); - - for (const user in loginusersData.users) { - if (loginusersData.users[user].mostrecent) { - const accountid = (new SteamID(user)).accountid; - this.loggedInUser = accountid; - log.info(`Got Steam user: ${accountid}`); - return resolve(accountid); - } - } - }); + } + + static getShortcutFile() { + return new Promise((resolve) => { + this.getSteamPath().then((steamPath) => { + this.getLoggedInUser().then((user) => { + const userdataPath = join(steamPath, 'userdata', String(user)); + const shortcutPath = join(userdataPath, 'config', 'shortcuts.vdf'); + resolve(shortcutPath); }); - } - - static getDefaultGridImage(appid) { - return `https://steamcdn-a.akamaihd.net/steam/apps/${appid}/header.jpg`; - } - - static getCustomGridImage(userdataGridPath, appid) { - const fileTypes = ['jpg', 'jpeg', 'png', 'tga']; - const basePath = join(userdataGridPath, String(appid)); - let image = false; - - fileTypes.some((ext) => { - const path = `${basePath}.${ext}`; - - if (fs.existsSync(path)) { - image = path; - return true; + }); + }); + } + + static addGrid(appId, url, onProgress = () => {}) { + return new Promise((resolve, reject) => { + this.getCurrentUserGridPath().then((userGridPath) => { + const imageUrl = url; + const imageExt = imageUrl.substr(imageUrl.lastIndexOf('.') + 1); + const dest = join(userGridPath, `${appId}.${imageExt}`); + + let cur = 0; + const data = new Stream(); + let progress = 0; + let lastProgress = 0; + https.get(url, (response) => { + const len = parseInt(response.headers['content-length'], 10); + + response.on('end', () => { + this.deleteCustomGridImage(userGridPath, appId); + fs.writeFileSync(dest, data.read()); + resolve(dest); + }); + + response.on('data', (chunk) => { + cur += chunk.length; + data.push(chunk); + progress = Math.round((cur / len) * 10) / 10; + if (progress !== lastProgress) { + lastProgress = progress; + onProgress(progress); } + }); + }).on('error', (err) => { + fs.unlink(dest); + reject(err); }); + }); + }); + } + + static addShortcuts(shortcuts) { + return new Promise((resolve) => { + this.getShortcutFile().then((shortcutPath) => { + shortcut.parseFile(shortcutPath, (err, items) => { + const newShorcuts = { + shortcuts: [], + }; + + let apps = []; + if (typeof items !== 'undefined') { + apps = items.shortcuts; + } + + shortcuts.forEach((value) => { + // Don't add dupes + apps.some((app) => { + const appid = this.generateAppId(app.exe, app.appname); + if (this.generateAppId(value.exe, value.name) === appid) { + return true; + } + return false; + }); - return image; - } + apps.push({ + appname: value.name, + exe: value.exe, + StartDir: value.startIn, + LaunchOptions: value.params, + icon: (typeof value.icon !== 'undefined' ? value.icon : ''), + IsHidden: false, + ShortcutPath: '', + AllowDesktopConfig: true, + OpenVR: false, + tags: (typeof value.tags !== 'undefined' ? value.tags : []), + }); + }); - static deleteCustomGridImage(userdataGridPath, appid) { - const imagePath = this.getCustomGridImage(userdataGridPath, appid); - if (imagePath) { - fs.unlinkSync(imagePath); - } - } + newShorcuts.shortcuts = apps; - static getShortcutFile() { - return new Promise((resolve) => { - this.getSteamPath().then((steamPath) => { - this.getLoggedInUser().then((user) => { - const userdataPath = join(steamPath, 'userdata', String(user)); - const shortcutPath = join(userdataPath, 'config', 'shortcuts.vdf'); - resolve(shortcutPath); - }); - }); + shortcut.writeFile(shortcutPath, newShorcuts, () => resolve()); }); - } + }); + }); + } + + 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'); + const localConfig = VDF.parse(fs.readFileSync(localConfigPath, 'utf-8')); + + let collections = {}; + if (localConfig.UserLocalConfigStore.WebStorage['user-collections']) { + collections = JSON.parse(localConfig.UserLocalConfigStore.WebStorage['user-collections'].replace(/\\/g, '')); + } - static addGrid(appId, url, onProgress = () => {}) { - return new Promise((resolve, reject) => { - this.getCurrentUserGridPath().then((userGridPath) => { - const image_url = url; - const image_ext = image_url.substr(image_url.lastIndexOf('.') + 1); - const dest = join(userGridPath, `${appId}.${image_ext}`); - - let cur = 0; - const data = new Stream(); - let progress = 0; - let lastProgress = 0; - https.get(url, (response) => { - const len = parseInt(response.headers['content-length'], 10); - - response.on('end', () => { - this.deleteCustomGridImage(userGridPath, appId); - fs.writeFileSync(dest, data.read()); - resolve(dest); - }); - - response.on('data', (chunk) => { - cur += chunk.length; - data.push(chunk); - progress = Math.round((cur / len) * 10) / 10; - if (progress !== lastProgress) { - lastProgress = progress; - onProgress(progress); - } - }); - }).on('error', (err) => { - fs.unlink(dest); - reject(err); + games.forEach((app) => { + const platformName = categoryId; + const appId = this.generateNewAppId(app.exe, app.name); + + // Create new category if it doesn't exist + const catKey = `sgdb-${platformName}`; // just use the name as the id + const platformCat = cats.get(catKey); + if (platformCat.is_deleted || !platformCat) { + cats.add(catKey, { + name: platformName, + added: [], }); + } + + // Create entry in localconfig.vdf + if (!collections[catKey]) { + collections[catKey] = { + id: catKey, + added: [], + removed: [], + }; + } + + // Add appids to localconfig.vdf + if (collections[catKey].added.indexOf(appId) === -1) { + // Only add if it doesn't exist already + collections[catKey].added.push(appId); + } }); - }); - } - static addShortcuts(shortcuts) { - return new Promise((resolve) => { - this.getShortcutFile().then((shortcutPath) => { - shortcut.parseFile(shortcutPath, (err, items) => { - const newShorcuts = { - 'shortcuts': [] - }; - - let apps = []; - if (typeof items != 'undefined') { - apps = items.shortcuts; - } - - shortcuts.forEach((value) => { - // Don't add dupes - for (let i = 0; i < apps.length; i++) { - const app = apps[i]; - const appid = this.generateAppId(app.exe, app.appname); - if (this.generateAppId(value.exe, value.name) === appid) { - return resolve(); - } - } - - apps.push({ - 'appname': value.name, - 'exe': value.exe, - 'StartDir': value.startIn, - 'LaunchOptions': value.params, - 'icon': (typeof value.icon !== 'undefined' ? value.icon : ''), - 'IsHidden': false, - 'ShortcutPath': '', - 'AllowDesktopConfig': true, - 'OpenVR': false, - 'tags': (typeof value.tags !== 'undefined' ? value.tags : []) - }); - }); - - newShorcuts.shortcuts = apps; - - shortcut.writeFile(shortcutPath, newShorcuts, () => resolve()); - }); - }); - }); - } + cats.save().then(() => { + localConfig.UserLocalConfigStore.WebStorage['user-collections'] = JSON.stringify(collections).replace(/"/g, '\\"'); // I hate Steam - static removeShortcut(name, executable) { - return new Promise((resolve) => { - this.getShortcutFile().then((shortcutPath) => { - shortcut.parseFile(shortcutPath, (err, items) => { - const newShorcuts = { - 'shortcuts': [] - }; - - let apps = []; - if (typeof items != 'undefined') { - apps = items.shortcuts; - } - - for (let i = 0; i < apps.length; i++) { - const app = apps[i]; - const appid = this.generateAppId(app.exe, app.appname); - if (this.generateAppId(executable, name) === appid) { - apps.splice(i, 1); - break; - } - } - - newShorcuts.shortcuts = apps; - shortcut.writeFile(shortcutPath, newShorcuts, () => resolve()); - }); + const newVDF = VDF.stringify(localConfig); + fs.writeFileSync(localConfigPath, newVDF); + cats.close(); + return resolve(); }); + }); + }).catch((err) => { + reject(err); }); - } + }); + }); + } } -export default Steam; \ No newline at end of file +export default Steam; diff --git a/src/js/TopBlur.js b/src/js/TopBlur.js deleted file mode 100644 index 77eaad2..0000000 --- a/src/js/TopBlur.js +++ /dev/null @@ -1,34 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import UWPNoise from '../img/uwp-noise.png'; - -class TopBlur extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- ); - } -} - -TopBlur.propTypes = { - additionalHeight: PropTypes.number.isRequired -}; - -TopBlur.defaultProps = { - additionalHeight: 0 -}; - -export default TopBlur; diff --git a/src/js/games.js b/src/js/games.js deleted file mode 100644 index 2993177..0000000 --- a/src/js/games.js +++ /dev/null @@ -1,247 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {Redirect} from 'react-router-dom'; -import Spinner from './spinner.js'; -import GridImage from './gridImage.js'; -import AutoSuggestBox from 'react-uwp/AutoSuggestBox'; -import AppBarButton from 'react-uwp/AppBarButton'; -import AppBarSeparator from 'react-uwp/AppBarSeparator'; -import Grid from './Grid'; -import Steam from './Steam'; -import queryString from 'query-string'; -import Fuse from 'fuse.js'; -import PubSub from 'pubsub-js'; -import {debounce} from 'lodash'; -import {forceCheck} from 'react-lazyload'; -import TopBlur from './TopBlur'; -const log = window.require('electron-log'); -import platformModules from './importers'; - -class Games extends React.Component { - constructor(props) { - super(props); - this.toSearch = this.toSearch.bind(this); - this.refreshGames = this.refreshGames.bind(this); - this.filterGames = this.filterGames.bind(this); - this.searchInput = debounce((searchTerm) => { - this.filterGames(searchTerm); - }, 300); - - const qs = this.props.location && queryString.parse(this.props.location.search); - this.scrollToTarget = qs.scrollto; - - this.zoom = 1; - - this.fetchedGames = {}; // Fetched games are stored here and shouldn't be changed unless a fetch is triggered again - this.platformNames = { - 'steam': 'Steam', - 'other': 'Other Games' - }; - - Object.keys(platformModules).forEach((module) => { - this.platformNames[platformModules[module].id] = platformModules[module].name; - }); - - this.state = { - error: null, - isLoaded: false, - isHover: false, - toSearch: false, - hasSteam: true, - items: {} - }; - } - - componentDidMount() { - if (Object.entries(this.state.items).length <= 0) { - Steam.getSteamPath().then(() => { - this.fetchGames(); - }).catch(() => { - log.warn('Steam is not installed'); - this.setState({ - hasSteam: false - }); - }); - } - } - - componentDidUpdate(prevProps, prevState) { - if (Object.entries(prevState.items).length === 0 && this.scrollToTarget) { - this.scrollTo(this.scrollToTarget); - PubSub.publish('showBack', false); - } - } - - fetchGames() { - const steamGamesPromise = Steam.getSteamGames(); - const nonSteamGamesPromise = Steam.getNonSteamGames(); - Promise.all([steamGamesPromise, nonSteamGamesPromise]).then((values) => { - const items = {steam: values[0], ...values[1]}; - // Sort games alphabetically - for (const platform in items) { - items[platform] = items[platform].sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); - } - - this.fetchedGames = items; - this.setState({ - isLoaded: true, - items: items - }); - }); - } - - toSearch(props) { - const parsedQs = queryString.stringify({ - game: props.name, - appid: props.appid, - type: props.gameType, - gameId: props.gameId, - platform: props.platform - }); - - const to = `/search/?${parsedQs}`; - this.setState({ - toSearch: - }); - } - - refreshGames() { - this.setState({ - isLoaded: false - }); - this.fetchGames(); - } - - filterGames(searchTerm) { - const items = Object.assign({}, this.fetchedGames); - if (searchTerm.trim() === '') { - this.setState({ - items: items - }); - return; - } - - Object.keys(items).forEach((platform) => { - const fuse = new Fuse(items[platform], { - shouldSort: true, - threshold: 0.6, - location: 0, - distance: 100, - maxPatternLength: 32, - minMatchCharLength: 1, - keys: [ - 'name' - ] - }); - items[platform] = fuse.search(searchTerm); - }); - this.setState({ - items: items - }); - - forceCheck(); // Recheck lazyload - } - - scrollTo(id) { - document.getElementById(`game-${id}`).scrollIntoView(true); - document.querySelector('#grids-container').scrollTop -= 75; // scroll down a bit cause grid goes under floating launcher name - } - - addNoCache(imageURI) { - if (!imageURI) { - return false; - } - - return `${imageURI}?${(new Date().getTime())}`; - } - - render() { - const {isLoaded, hasSteam, items} = this.state; - - if (!hasSteam) { - return ( -
- Steam installation not found. -
- ); - } - - if (!isLoaded) { - return ; - } - - if (this.state.toSearch) { - return this.state.toSearch; - } - - return ( -
- -
- - - -
-
- {Object.keys(items).map((platform) => ( -
-
- {this.platformNames[platform]} -
- - {items[platform].map((item) => { - const imageURI = this.addNoCache((item.imageURI)); - return ( - // id attribute is used as a scroll target after a search -
- -
- ); - })} -
-
- ))} -
-
- ); - } -} - -Games.propTypes = { - location: PropTypes.object -}; -Games.contextTypes = { theme: PropTypes.object }; -export default Games; diff --git a/src/js/games.jsx b/src/js/games.jsx new file mode 100644 index 0000000..a2a2dc0 --- /dev/null +++ b/src/js/games.jsx @@ -0,0 +1,254 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Redirect } from 'react-router-dom'; +import AutoSuggestBox from 'react-uwp/AutoSuggestBox'; +import AppBarButton from 'react-uwp/AppBarButton'; +import AppBarSeparator from 'react-uwp/AppBarSeparator'; +import Fuse from 'fuse.js'; +import PubSub from 'pubsub-js'; +import { debounce } from 'lodash'; +import { forceCheck } from 'react-lazyload'; +import queryString from 'query-string'; +import Grid from './Components/Grid'; +import Spinner from './Components/spinner'; +import GridImage from './Components/gridImage'; +import Steam from './Steam'; +import TopBlur from './Components/TopBlur'; +import platformModules from './importers'; + +const log = window.require('electron-log'); + +class Games extends React.Component { + constructor(props) { + super(props); + this.toSearch = this.toSearch.bind(this); + this.refreshGames = this.refreshGames.bind(this); + this.filterGames = this.filterGames.bind(this); + this.searchInput = debounce((searchTerm) => { + this.filterGames(searchTerm); + }, 300); + + const { location } = this.props; + + const qs = location && queryString.parse(location.search); + this.scrollToTarget = qs.scrollto; + + this.zoom = 1; + + // Fetched games are stored here and shouldn't be changed unless a fetch is triggered again + this.fetchedGames = {}; + this.platformNames = { + steam: 'Steam', + other: 'Other Games', + }; + + Object.keys(platformModules).forEach((module) => { + this.platformNames[platformModules[module].id] = platformModules[module].name; + }); + + this.state = { + isLoaded: false, + toSearch: false, + hasSteam: true, + items: {}, + }; + } + + componentDidMount() { + const { items } = this.state; + + if (Object.entries(items).length <= 0) { + Steam.getSteamPath().then(() => { + this.fetchGames(); + }).catch(() => { + log.warn('Steam is not installed'); + this.setState({ hasSteam: false }); + }); + } + } + + componentDidUpdate(prevProps, prevState) { + if (Object.entries(prevState.items).length === 0 && this.scrollToTarget) { + this.scrollTo(this.scrollToTarget); + PubSub.publish('showBack', false); + } + } + + fetchGames() { + const steamGamesPromise = Steam.getSteamGames(); + const nonSteamGamesPromise = Steam.getNonSteamGames(); + Promise.all([steamGamesPromise, nonSteamGamesPromise]).then((values) => { + const items = { steam: values[0], ...values[1] }; + // Sort games alphabetically + Object.keys(items).forEach((platform) => { + items[platform] = items[platform].sort((a, b) => { + if (a.name > b.name) { + return 1; + } + + return ((b.name > a.name) ? -1 : 0); + }); + }); + + this.fetchedGames = items; + this.setState({ + isLoaded: true, + items, + }); + }); + } + + toSearch(props) { + const parsedQs = queryString.stringify({ + game: props.name, + appid: props.appid, + type: props.gameType, + gameId: props.gameId, + platform: props.platform, + }); + + const to = `/search/?${parsedQs}`; + this.setState({ toSearch: }); + } + + refreshGames() { + this.setState({ isLoaded: false }); + this.fetchGames(); + } + + filterGames(searchTerm) { + const items = { ...this.fetchedGames }; + if (searchTerm.trim() === '') { + this.setState({ items }); + return; + } + + Object.keys(items).forEach((platform) => { + const fuse = new Fuse(items[platform], { + shouldSort: true, + threshold: 0.6, + location: 0, + distance: 100, + maxPatternLength: 32, + minMatchCharLength: 1, + keys: [ + 'name', + ], + }); + items[platform] = fuse.search(searchTerm); + }); + this.setState({ items }); + + forceCheck(); // Recheck lazyload + } + + addNoCache(imageURI) { + if (!imageURI) { + return false; + } + + return `${imageURI}?${(new Date().getTime())}`; + } + + scrollTo(id) { + document.getElementById(`game-${id}`).scrollIntoView(true); + document.querySelector('#grids-container').scrollTop -= 75; // scroll down a bit cause grid goes under floating launcher name + } + + render() { + const { + isLoaded, + hasSteam, + items, + toSearch, + } = this.state; + const { theme } = this.context; + + if (!hasSteam) { + return ( +
+ Steam installation not found. +
+ ); + } + + if (!isLoaded) { + return ; + } + + if (toSearch) { + return toSearch; + } + + return ( +
+ +
+ + + +
+
+ {Object.keys(items).map((platform) => ( +
+
+ {this.platformNames[platform]} +
+ + {items[platform].map((item) => { + const imageURI = this.addNoCache((item.imageURI)); + return ( + // id attribute is used as a scroll target after a search +
+ +
+ ); + })} +
+
+ ))} +
+
+ ); + } +} + +Games.propTypes = { + location: PropTypes.object +}; +Games.contextTypes = { theme: PropTypes.object }; +export default Games; diff --git a/src/js/gridImage.js b/src/js/gridImage.js deleted file mode 100644 index c4a316a..0000000 --- a/src/js/gridImage.js +++ /dev/null @@ -1,137 +0,0 @@ -import React from 'react'; -import ProgressBar from 'react-uwp/ProgressBar'; -import {CSSTransitionGroup} from 'react-transition-group'; -import PropTypes from 'prop-types'; -const ReactLazyLoad = require('react-lazyload').default; - -class GridImage extends React.Component { - constructor(props) { - super(props); - - this.gridWidth = 300 * this.props.zoom; - this.gridHeight = 140 * this.props.zoom; - this.onGridClick = this.props.onGridClick; - this.handleClick = this.handleClick.bind(this); - } - - shouldComponentUpdate(nextProps) { - return !(this.props.progress === nextProps.progress); - } - - handleClick() { - this.onGridClick(this.props); - } - - render() { - let progressBar =
; - if (this.props.progress) { - progressBar = ( -
- -
- ); - } - - - let image = ''; - if (this.props.image) { - image = ( - - - - - - ); - } - - return ( -
- {image} - -
- {this.props.name} -
- -
- {this.props.author && - Grid by: {this.props.author} - } -
- - {progressBar} -
- ); - } -} - -GridImage.propTypes = { - name: PropTypes.string, - appid: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]), - index: PropTypes.number, - gameType: PropTypes.string, - gameId: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]), - platform: PropTypes.string, - author: PropTypes.string, - zoom: PropTypes.number, - progress: PropTypes.number, - image: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.bool - ]), - onGridClick: PropTypes.func -}; -GridImage.contextTypes = { theme: PropTypes.object }; - -export default GridImage; diff --git a/src/js/importers.js b/src/js/importers.js index 9fedeef..7368cd1 100644 --- a/src/js/importers.js +++ b/src/js/importers.js @@ -1,20 +1,22 @@ const importers = {}; -const importAll = (r) => r.keys().forEach((key) => importers[key] = r(key)); +const importAll = (r) => r.keys().forEach((key) => { + importers[key] = r(key); +}); const context = require.context('./importers/', false, /\.js$/); importAll(context); export default importers; function getOfficial() { - const officialList = []; - Object.keys(importers).forEach((module) => { - if (importers[module].official) { - officialList.push(importers[module].id); - } - }); - return officialList; + const officialList = []; + Object.keys(importers).forEach((module) => { + if (importers[module].official) { + officialList.push(importers[module].id); + } + }); + return officialList; } // Array of imprter ids -export const officialList = getOfficial(); \ No newline at end of file +export const officialList = getOfficial(); diff --git a/src/js/importers/BattleNet.js b/src/js/importers/BattleNet.js index ed28962..d87bf35 100644 --- a/src/js/importers/BattleNet.js +++ b/src/js/importers/BattleNet.js @@ -1,164 +1,163 @@ +import decoder from 'blizzard-product-parser/src/js/database'; // Workaround for badly configured lib +import { PowerShell, LauncherAutoClose } from '../paths'; + const Registry = window.require('winreg'); const fs = window.require('fs'); const path = window.require('path'); const log = window.require('electron-log'); -import { PowerShell, LauncherAutoClose } from '../paths.js'; -import decoder from 'blizzard-product-parser/src/js/database'; // Workaround for badly configured lib const BNET_GAMES = { - 'd3': { - name: 'Diablo III', - launchId: 'D3', - exes: ['Diablo III', 'Diablo III64'], - icon: 'Diablo III Launcher.exe' - }, - 'dst2': { - name: 'Destiny 2', - launchId: 'DST2', - exes: ['destiny2'], - icon: 'Destiny 2 Launcher.exe' - }, - 'hero': { - name: 'Heroes of the Storm', - launchId: 'Hero', - exes: ['HeroesSwitcher', 'HeroesSwitcher_x64'], - icon: 'Heroes of the Storm.exe' - }, - 'odin': { - name: 'Call of Duty: Modern Warfare', - launchId: 'ODIN', - exes: ['codmw2019'], - icon: 'Modern Warfare Launcher.exe' - }, - 'pro': { - name: 'Overwatch', - launchId: 'Pro', - exes: ['Overwatch'], - icon: 'Overwatch Launcher.exe' - }, - 's1': { - name: 'Starcraft Remastered', - launchId: 'S1', - exes: ['StarCraft'], - icon: 'StarCraft Launcher.exe' - }, - 's2': { - name: 'Starcraft 2', - launchId: 'S2', - exes: ['SC2Switcher_x64', 'SC2Switcher'], - icon: 'StarCraft II.exe' - }, - 'viper': { - name: 'Call of Duty: Black Ops 4', - launchId: 'VIPR', - exes: ['BlackOps4', 'BlackOps4_boot'], - icon: 'Black Ops 4 Launcher.exe' - }, - 'w3': { - name: 'Warcraft 3: Reforged', - launchId: 'W3', - exes: ['Warcraft III'], - icon: 'Warcraft III.exe' - }, - 'hsb': { - name: 'Hearthstone', - launchId: 'WTCG', - exes: ['Hearthstone'], - icon: 'Hearthstone.exe' - }, - 'wow': { - name: 'World of Warcraft', - launchId: 'WoW', - exes: ['Wow'], - icon: 'World of Warcraft Launcher.exe' - } + d3: { + name: 'Diablo III', + launchId: 'D3', + exes: ['Diablo III', 'Diablo III64'], + icon: 'Diablo III Launcher.exe', + }, + dst2: { + name: 'Destiny 2', + launchId: 'DST2', + exes: ['destiny2'], + icon: 'Destiny 2 Launcher.exe', + }, + hero: { + name: 'Heroes of the Storm', + launchId: 'Hero', + exes: ['HeroesSwitcher', 'HeroesSwitcher_x64'], + icon: 'Heroes of the Storm.exe', + }, + odin: { + name: 'Call of Duty: Modern Warfare', + launchId: 'ODIN', + exes: ['codmw2019'], + icon: 'Modern Warfare Launcher.exe', + }, + pro: { + name: 'Overwatch', + launchId: 'Pro', + exes: ['Overwatch'], + icon: 'Overwatch Launcher.exe', + }, + s1: { + name: 'Starcraft Remastered', + launchId: 'S1', + exes: ['StarCraft'], + icon: 'StarCraft Launcher.exe', + }, + s2: { + name: 'Starcraft 2', + launchId: 'S2', + exes: ['SC2Switcher_x64', 'SC2Switcher'], + icon: 'StarCraft II.exe', + }, + viper: { + name: 'Call of Duty: Black Ops 4', + launchId: 'VIPR', + exes: ['BlackOps4', 'BlackOps4_boot'], + icon: 'Black Ops 4 Launcher.exe', + }, + w3: { + name: 'Warcraft 3: Reforged', + launchId: 'W3', + exes: ['Warcraft III'], + icon: 'Warcraft III.exe', + }, + hsb: { + name: 'Hearthstone', + launchId: 'WTCG', + exes: ['Hearthstone'], + icon: 'Hearthstone.exe', + }, + wow: { + name: 'World of Warcraft', + launchId: 'WoW', + exes: ['Wow'], + icon: 'World of Warcraft Launcher.exe', + }, }; class BattleNet { - static isInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Battle.net' - }); + static isInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Battle.net', + }); - reg.valueExists('', (err, exists) => { - if (err) { - reject(new Error('Could not check if the Battle.net is installed.')); - } + reg.valueExists('', (err, exists) => { + if (err) { + reject(new Error('Could not check if the Battle.net is installed.')); + } - resolve(exists); - }); - }); - } + resolve(exists); + }); + }); + } - static getBattlenetPath() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Battle.net' - }); + static getBattlenetPath() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Battle.net', + }); - reg.values((err, items) => { - let bnetPath = false; - items.forEach((item) => { - if (item.name === 'InstallLocation') { - bnetPath = item.value; - } - }); - - if (bnetPath) { - resolve(bnetPath); - } else { - reject(new Error('Could not Battle.net path.')); - } - }); + reg.values((err, items) => { + let bnetPath = false; + items.forEach((item) => { + if (item.name === 'InstallLocation') { + bnetPath = item.value; + } }); - } - static getGames() { - return new Promise((resolve, reject) => { - log.info('Import: Started bnet'); - this.getBattlenetPath().then((bnetPath) => { - const games = []; - const bnetExe = path.join(bnetPath, 'Battle.net.exe'); + if (bnetPath) { + resolve(bnetPath); + } else { + reject(new Error('Could not Battle.net path.')); + } + }); + }); + } - try { - const decoded = decoder.decode(fs.readFileSync('C:\\ProgramData\\Battle.net\\Agent\\product.db')); - const installed = decoded.productInstall.filter((product) => !(product.uid === 'battle.net' || product.uid === 'agent')); // Filter out non-games + static getGames() { + return new Promise((resolve, reject) => { + log.info('Import: Started bnet'); + this.getBattlenetPath().then((bnetPath) => { + const games = []; + const bnetExe = path.join(bnetPath, 'Battle.net.exe'); - installed.forEach((product) => { - const gameId = product.uid; - const productCode = product.productCode.toLowerCase(); - if (BNET_GAMES[productCode]) { - const launchId = BNET_GAMES[productCode].launchId; - const name = BNET_GAMES[productCode].name; - const exes = BNET_GAMES[productCode].exes; - const icon = path.join(product.settings.installPath, BNET_GAMES[productCode].icon); - games.push({ - id: gameId, - name: name, - exe: `"${PowerShell}"`, - icon: `"${icon}"`, - startIn: `"${bnetPath}"`, - params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\" -launcher \\"battle.net\\" -game \\"${exes.join('\\",\\"')}\\" -launchcmd \\"dummy\\" -bnet $True -bnetpath \\"${bnetExe}\\" -bnetlaunchid \\"${launchId}\\""`, - platform: 'bnet' - }); - } - }); - log.info('Import: Completed bnet'); - resolve(games); - } catch(err) { - reject(err); - } - }).catch((err) => reject(err)); - }); - } + try { + const decoded = decoder.decode(fs.readFileSync('C:\\ProgramData\\Battle.net\\Agent\\product.db')); + const installed = decoded.productInstall.filter((product) => !(product.uid === 'battle.net' || product.uid === 'agent')); // Filter out non-games + + installed.forEach((product) => { + const gameId = product.uid; + const productCode = product.productCode.toLowerCase(); + if (BNET_GAMES[productCode]) { + const { launchId, name, exes } = BNET_GAMES[productCode]; + const icon = path.join(product.settings.installPath, BNET_GAMES[productCode].icon); + games.push({ + id: gameId, + name, + exe: `"${PowerShell}"`, + icon: `"${icon}"`, + startIn: `"${bnetPath}"`, + params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\" -launcher \\"battle.net\\" -game \\"${exes.join('\\",\\"')}\\" -launchcmd \\"dummy\\" -bnet $True -bnetpath \\"${bnetExe}\\" -bnetlaunchid \\"${launchId}\\""`, + platform: 'bnet', + }); + } + }); + log.info('Import: Completed bnet'); + resolve(games); + } catch (err) { + reject(err); + } + }).catch((err) => reject(err)); + }); + } } export default BattleNet; export const name = 'Blizzard Battle.net'; export const id = 'bnet'; -export const official = true; \ No newline at end of file +export const official = true; diff --git a/src/js/importers/Epic.js b/src/js/importers/Epic.js index baaba2c..70ae8a4 100644 --- a/src/js/importers/Epic.js +++ b/src/js/importers/Epic.js @@ -1,93 +1,94 @@ +import { PowerShell, LauncherAutoClose } from '../paths'; + const Registry = window.require('winreg'); const fs = window.require('fs'); const path = window.require('path'); const jsonminify = window.require('jsonminify'); -const {arch} = window.require('os'); +const { arch } = window.require('os'); const log = window.require('electron-log'); -import { PowerShell, LauncherAutoClose } from '../paths.js'; class Epic { - static isInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\EpicGames\\Unreal Engine' - }); + static isInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\EpicGames\\Unreal Engine', + }); - reg.get('INSTALLDIR', (err, installDir) => { - if (err) { - if (err.code == 1) { - return resolve(false); - } - reject(new Error('Could not check if Epic Games Launcher is installed.')); - } + reg.get('INSTALLDIR', (err, installDir) => { + if (err) { + if (err.code === 1) { + return resolve(false); + } + reject(new Error('Could not check if Epic Games Launcher is installed.')); + } - const exeExists = fs.existsSync(path.join(installDir.value, 'Launcher', 'Engine', 'Binaries', 'Win32', 'EpicGamesLauncher.exe')); - resolve(exeExists); - }); - }); - } + const exeExists = fs.existsSync(path.join(installDir.value, 'Launcher', 'Engine', 'Binaries', 'Win32', 'EpicGamesLauncher.exe')); + return resolve(exeExists); + }); + }); + } - static getEpicPath() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\EpicGames\\Unreal Engine' - }); + static getEpicPath() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\EpicGames\\Unreal Engine', + }); - reg.get('INSTALLDIR', (err, installDir) => { - if (err) { - reject(new Error('Could not find Epic Games Launcher path.')); - } + reg.get('INSTALLDIR', (err, installDir) => { + if (err) { + reject(new Error('Could not find Epic Games Launcher path.')); + } - resolve(installDir.value); - }); - }); - } + resolve(installDir.value); + }); + }); + } - static getGames() { - return new Promise((resolve, reject) => { - log.info('Import: Started egs'); - this.getEpicPath().then((epicPath) => { - const games = []; - let binFolder; - if (arch == 'ia32') { - binFolder = 'Win32'; - } else if (arch == 'x64') { - binFolder = 'Win64'; - } - const binaryPath = path.join(epicPath, 'Launcher', 'Portal', 'Binaries', binFolder); - const manifestsDir = 'C:\\ProgramData\\Epic\\EpicGamesLauncher\\Data\\Manifests'; + static getGames() { + return new Promise((resolve, reject) => { + log.info('Import: Started egs'); + this.getEpicPath().then((epicPath) => { + const games = []; + let binFolder; + if (arch === 'ia32') { + binFolder = 'Win32'; + } else if (arch === 'x64') { + binFolder = 'Win64'; + } + const binaryPath = path.join(epicPath, 'Launcher', 'Portal', 'Binaries', binFolder); + const manifestsDir = 'C:\\ProgramData\\Epic\\EpicGamesLauncher\\Data\\Manifests'; - if (!fs.existsSync(manifestsDir)) { - return resolve([]); - } + if (!fs.existsSync(manifestsDir)) { + return resolve([]); + } - fs.readdirSync(manifestsDir).forEach((file) => { - if (path.extname(file) === '.item') { - const launcherDataStr = fs.readFileSync(path.join(manifestsDir, file)).toString(); - const parsed = JSON.parse(jsonminify(launcherDataStr)); - games.push({ - id: parsed.AppName, - name: parsed.DisplayName, - exe: `"${PowerShell}"`, - icon: `"${path.join(parsed.InstallLocation, parsed.LaunchExecutable)}"`, - startIn: `"${binaryPath}"`, - params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\"" -launcher \\"EpicGamesLauncher\\" -game \\"${path.parse(parsed.LaunchExecutable).name}\\" -launchcmd \\"com.epicgames.launcher://apps/${parsed.AppName}?action=launch&silent=true\\""`, - platform: 'egs' - }); - } - }); - log.info('Import: Completed egs'); - resolve(games); - }).catch((err) => reject(err)); + fs.readdirSync(manifestsDir).forEach((file) => { + if (path.extname(file) === '.item') { + const launcherDataStr = fs.readFileSync(path.join(manifestsDir, file)).toString(); + const parsed = JSON.parse(jsonminify(launcherDataStr)); + games.push({ + id: parsed.AppName, + name: parsed.DisplayName, + exe: `"${PowerShell}"`, + icon: `"${path.join(parsed.InstallLocation, parsed.LaunchExecutable)}"`, + startIn: `"${binaryPath}"`, + params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\"" -launcher \\"EpicGamesLauncher\\" -game \\"${path.parse(parsed.LaunchExecutable).name}\\" -launchcmd \\"com.epicgames.launcher://apps/${parsed.AppName}?action=launch&silent=true\\""`, + platform: 'egs', + }); + } }); - } + log.info('Import: Completed egs'); + return resolve(games); + }).catch((err) => reject(err)); + }); + } } export default Epic; export const name = 'Epic Games Launcher'; export const id = 'egs'; -export const official = true; \ No newline at end of file +export const official = true; diff --git a/src/js/importers/Gog.js b/src/js/importers/Gog.js index f3076b2..8702030 100644 --- a/src/js/importers/Gog.js +++ b/src/js/importers/Gog.js @@ -4,117 +4,118 @@ const promiseReflect = window.require('promise-reflect'); const log = window.require('electron-log'); class Gog { - static isInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\GOG.com\\GalaxyClient\\paths' - }); + static isInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\GOG.com\\GalaxyClient\\paths', + }); - reg.get('client', (err, installDir) => { - if (err) { - if (err.code == 1) { - return resolve(false); - } - reject(new Error('Could not check if GOG Galaxy is installed.')); - } - const dirExists = fs.existsSync(installDir.value); - resolve(dirExists); - }); - }); - } + reg.get('client', (err, installDir) => { + if (err) { + if (err.code === 1) { + return resolve(false); + } + reject(new Error('Could not check if GOG Galaxy is installed.')); + } + const dirExists = fs.existsSync(installDir.value); + return resolve(dirExists); + }); + }); + } - static getGogPath() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\GOG.com\\GalaxyClient\\paths' - }); + static getGogPath() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\GOG.com\\GalaxyClient\\paths', + }); - reg.get('client', (err, installDir) => { - if (err) { - reject(new Error('Could not find GOG Galaxy path.')); - } + reg.get('client', (err, installDir) => { + if (err) { + reject(new Error('Could not find GOG Galaxy path.')); + } - resolve(installDir.value); - }); - }); - } + resolve(installDir.value); + }); + }); + } - static _processRegKey(key) { - return new Promise((resolve, reject) => { - key.get('dependsOn', (err, dependsOn) => { - if (dependsOn == null) { - key.values((err, items) => { - const game = { - platform: 'gog' - }; + static _processRegKey(key) { + return new Promise((resolve, reject) => { + key.get('dependsOn', (err, dependsOn) => { + if (dependsOn == null) { + key.values((errItems, items) => { + const game = { + platform: 'gog', + }; - items.forEach((item) => { - if (item.name === 'gameID' || item.name === 'GAMEID') { - game.id = item.value; - } + items.forEach((item) => { + if (item.name === 'gameID' || item.name === 'GAMEID') { + game.id = item.value; + } - if (item.name === 'gameName' || item.name === 'GAMENAME') { - game.name = item.value; - } + if (item.name === 'gameName' || item.name === 'GAMENAME') { + game.name = item.value; + } - if (item.name === 'exe' || item.name === 'EXE') { - game.exe = `"${item.value}"`; - } + if (item.name === 'exe' || item.name === 'EXE') { + game.exe = `"${item.value}"`; + } - if (item.name === 'launchParam' || item.name === 'LAUNCHPARAM') { - game.params = item.value; - } + if (item.name === 'launchParam' || item.name === 'LAUNCHPARAM') { + game.params = item.value; + } - if (item.name === 'path' || item.name === 'PATH') { - game.startIn = `"${item.value}"`; - } - }); - resolve(game); - }); - } else { - reject(key); - } + if (item.name === 'path' || item.name === 'PATH') { + game.startIn = `"${item.value}"`; + } }); - }); - } + resolve(game); + }); + } else { + reject(key); + } + }); + }); + } - static getGames() { - return new Promise((resolve, reject) => { - log.info('Import: Started gog'); - this.getGogPath().then(() => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\GOG.com\\Games' - }); + static getGames() { + return new Promise((resolve, reject) => { + log.info('Import: Started gog'); + this.getGogPath().then(() => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\GOG.com\\Games', + }); - reg.keys((err, keys) => { - if (err) { - reject(new Error('Could not get GOG games.')); - } + reg.keys((err, keys) => { + if (err) { + reject(new Error('Could not get GOG games.')); + } - if (keys) { - const promiseArr = keys.map((key) => this._processRegKey(key).then((res) => res)); - Promise.all(promiseArr.map(promiseReflect)) - .then((results) => results.filter((result) => result.status === 'resolved').map((result) => result.data)) - .then((results) => { - log.info('Import: Completed gog'); - resolve(results); - }); - } else { - return resolve([]); - } - }); - }).catch((err) => reject(err)); + if (keys) { + const promiseArr = keys.map((key) => this._processRegKey(key).then((res) => res)); + Promise.all(promiseArr.map(promiseReflect)) + .then((results) => results.filter((result) => result.status === 'resolved').map((result) => result.data)) + .then((results) => { + log.info('Import: Completed gog'); + resolve(results); + }); + } else { + return resolve([]); + } + return false; }); - } + }).catch((err) => reject(err)); + }); + } } export default Gog; export const name = 'GOG.com'; export const id = 'gog'; -export const official = true; \ No newline at end of file +export const official = true; diff --git a/src/js/importers/Origin.js b/src/js/importers/Origin.js index 9f06d4a..2238ec7 100644 --- a/src/js/importers/Origin.js +++ b/src/js/importers/Origin.js @@ -1,179 +1,181 @@ +import { PowerShell, LauncherAutoClose } from '../paths'; + const Registry = window.require('winreg'); const fs = window.require('fs'); const path = window.require('path'); const querystring = window.require('querystring'); -const xml2js = window.require('xml-js').xml2js; +const { xml2js } = window.require('xml-js'); const iconv = window.require('iconv-lite'); const log = window.require('electron-log'); -import { PowerShell, LauncherAutoClose } from '../paths.js'; class Origin { - static isInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Origin' - }); - - reg.valueExists('', (err, exists) => { - if (err) { - reject(new Error('Could not check if Origin is installed.')); - } - - resolve(exists); - }); - }); - } - - static getOriginPath() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Origin' - }); - - reg.values((err, items) => { - if (err) { - reject(new Error('Could not find Origin path.')); - } + static isInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Origin', + }); + + reg.valueExists('', (err, exists) => { + if (err) { + reject(new Error('Could not check if Origin is installed.')); + } - let originPath = false; + resolve(exists); + }); + }); + } + + static getOriginPath() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Origin', + }); + + reg.values((err, items) => { + if (err) { + reject(new Error('Could not find Origin path.')); + } - items.forEach((item) => { - if (item.name === 'ClientPath') { - originPath = item.value; - } - }); + let originPath = false; - if (originPath) { - resolve(originPath); - } else { - reject(new Error('Could not find Origin path.')); - } - }); + items.forEach((item) => { + if (item.name === 'ClientPath') { + originPath = item.value; + } }); - } - static _parseRuntime(runtime) { - const exeDefs = []; - if (runtime.launcher) { - if (runtime.launcher.filePath) { - // Only one exe - exeDefs.push(runtime.launcher.filePath._text); - } else if (runtime.launcher[0] && runtime.launcher[0].filePath) { - // Multiple exes - runtime.launcher.forEach((exe) => { - if (exe.filePath) { - exeDefs.push(exe.filePath._text); - } - }); - } else { - return false; - } + if (originPath) { + resolve(originPath); } else { - return false; + reject(new Error('Could not find Origin path.')); } - - // remove everything in [] cause we only need the exe name - return exeDefs.map((exe) => (exe.replace(/\[.+\]/g, ''))); + }); + }); + } + + static _parseRuntime(runtime) { + const exeDefs = []; + if (runtime.launcher) { + if (runtime.launcher.filePath) { + // Only one exe + exeDefs.push(runtime.launcher.filePath._text); + } else if (runtime.launcher[0] && runtime.launcher[0].filePath) { + // Multiple exes + runtime.launcher.forEach((exe) => { + if (exe.filePath) { + exeDefs.push(exe.filePath._text); + } + }); + } else { + return false; + } + } else { + return false; } - static getGames() { - return new Promise((resolve, reject) => { - log.info('Import: Started origin'); - this.getOriginPath().then((originPath) => { - const originDataPath = 'C:\\ProgramData\\Origin'; - const games = []; - - if (fs.existsSync(path.join(originDataPath, 'LocalContent'))) { - fs.readdirSync(path.join(originDataPath, 'LocalContent')).forEach((folder) => { - const manifestFolder = path.join(originDataPath, 'LocalContent', folder); - if (fs.lstatSync(manifestFolder).isDirectory()) { - fs.readdirSync(manifestFolder).some((file) => { - // Get first file with .mfst extension - if (path.extname(file) === '.mfst') { - // .mfst file is just a text file with a query string - const manifestFile = path.join(originDataPath, 'LocalContent', folder, file); - const manifestStr = fs.readFileSync(manifestFile).toString(); - const manifestStrParsed = querystring.parse(manifestStr); - // Check if has a "dipinstallpath" param - if (manifestStrParsed.dipinstallpath) { - const installerDataPath = path.join(manifestStrParsed.dipinstallpath, '__Installer', 'installerdata.xml'); - // If __Installer/installerdata.xml file exists in the install dir - if (fs.existsSync(installerDataPath)) { - // Parse installerdata.xml file - let xml, executables, name; - try { - const installerDataFile = fs.readFileSync(installerDataPath); - try { - xml = xml2js(iconv.decode(installerDataFile, 'utf8'), {compact: true}); - } catch (err) { - xml = xml2js(iconv.decode(installerDataFile, 'utf16'), {compact: true}); - } - } catch (err) { - return reject(`Could not parse installerdata.xml for ${path.basename(folder)}`); - } - - if (xml.DiPManifest) { - if (xml.DiPManifest.runtime) { - executables = this._parseRuntime(xml.DiPManifest.runtime); - } - if (xml.DiPManifest.gameTitles.gameTitle) { - if (xml.DiPManifest.gameTitles.gameTitle._text) { - name = xml.DiPManifest.gameTitles.gameTitle._text; - } else if (xml.DiPManifest.gameTitles.gameTitle[0]) { - name = xml.DiPManifest.gameTitles.gameTitle[0]._text; - } - } - } else if (xml.game) { - if (xml.game.runtime) { - executables = this._parseRuntime(xml.game.runtime); - } - if (xml.game.metadata.localeInfo) { - if (xml.game.metadata.localeInfo.title) { - name = xml.game.metadata.localeInfo.title._text; - } else if (xml.game.metadata.localeInfo[0]) { - name = xml.game.metadata.localeInfo[0].title._text; - } - } - } - - if (!name) { - return true; - } - - if (executables) { - const watchedExes = executables.map((x) => path.parse(path.basename(x)).name); - games.push({ - id: manifestStrParsed.id, - name: name, - exe: `"${PowerShell}"`, - icon: `"${path.join(manifestStrParsed.dipinstallpath, executables[0])}"`, - startIn: `"${path.dirname(originPath)}"`, - params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\"" -launcher \\"Origin\\" -game \\"${watchedExes.join('\\",\\"')}\\" -launchcmd \\"origin://launchgamejump/${manifestStrParsed.id}\\""`, - platform: 'origin' - }); - } - return true; - } - } - } - }); + // remove everything in [] cause we only need the exe name + return exeDefs.map((exe) => (exe.replace(/\[.+\]/g, ''))); + } + + static getGames() { + return new Promise((resolve, reject) => { + log.info('Import: Started origin'); + this.getOriginPath().then((originPath) => { + const originDataPath = 'C:\\ProgramData\\Origin'; + const games = []; + + if (fs.existsSync(path.join(originDataPath, 'LocalContent'))) { + fs.readdirSync(path.join(originDataPath, 'LocalContent')).forEach((folder) => { + const manifestFolder = path.join(originDataPath, 'LocalContent', folder); + if (fs.lstatSync(manifestFolder).isDirectory()) { + fs.readdirSync(manifestFolder).some((file) => { + // Get first file with .mfst extension + if (path.extname(file) === '.mfst') { + // .mfst file is just a text file with a query string + const manifestFile = path.join(originDataPath, 'LocalContent', folder, file); + const manifestStr = fs.readFileSync(manifestFile).toString(); + const manifestStrParsed = querystring.parse(manifestStr); + // Check if has a "dipinstallpath" param + if (manifestStrParsed.dipinstallpath) { + const installerDataPath = path.join(manifestStrParsed.dipinstallpath, '__Installer', 'installerdata.xml'); + // If __Installer/installerdata.xml file exists in the install dir + if (fs.existsSync(installerDataPath)) { + // Parse installerdata.xml file + let xml; let executables; let + name; + try { + const installerDataFile = fs.readFileSync(installerDataPath); + try { + xml = xml2js(iconv.decode(installerDataFile, 'utf8'), { compact: true }); + } catch (err) { + xml = xml2js(iconv.decode(installerDataFile, 'utf16'), { compact: true }); + } + } catch (err) { + return reject(new Error(`Could not parse installerdata.xml for ${path.basename(folder)}`)); + } + + if (xml.DiPManifest) { + if (xml.DiPManifest.runtime) { + executables = this._parseRuntime(xml.DiPManifest.runtime); + } + if (xml.DiPManifest.gameTitles.gameTitle) { + if (xml.DiPManifest.gameTitles.gameTitle._text) { + name = xml.DiPManifest.gameTitles.gameTitle._text; + } else if (xml.DiPManifest.gameTitles.gameTitle[0]) { + name = xml.DiPManifest.gameTitles.gameTitle[0]._text; + } + } + } else if (xml.game) { + if (xml.game.runtime) { + executables = this._parseRuntime(xml.game.runtime); + } + if (xml.game.metadata.localeInfo) { + if (xml.game.metadata.localeInfo.title) { + name = xml.game.metadata.localeInfo.title._text; + } else if (xml.game.metadata.localeInfo[0]) { + name = xml.game.metadata.localeInfo[0].title._text; + } } - }); - log.info('Import: Completed origin'); - resolve(games); - } else { - reject('Could not find Origin content folder.'); + } + + if (!name) { + return true; + } + + if (executables) { + const watchedExes = executables.map((x) => path.parse(path.basename(x)).name); + games.push({ + id: manifestStrParsed.id, + name, + exe: `"${PowerShell}"`, + icon: `"${path.join(manifestStrParsed.dipinstallpath, executables[0])}"`, + startIn: `"${path.dirname(originPath)}"`, + params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\"" -launcher \\"Origin\\" -game \\"${watchedExes.join('\\",\\"')}\\" -launchcmd \\"origin://launchgamejump/${manifestStrParsed.id}\\""`, + platform: 'origin', + }); + } + return true; + } + } } - }); - }); - } + }); + } + }); + log.info('Import: Completed origin'); + resolve(games); + } else { + reject(new Error('Could not find Origin content folder.')); + } + }); + }); + } } export default Origin; export const name = 'Origin'; export const id = 'origin'; -export const official = true; \ No newline at end of file +export const official = true; diff --git a/src/js/importers/Uplay.js b/src/js/importers/Uplay.js index c609d3f..c51b4d1 100644 --- a/src/js/importers/Uplay.js +++ b/src/js/importers/Uplay.js @@ -1,339 +1,341 @@ +import { PowerShell, LauncherAutoClose } from '../paths'; + const Registry = window.require('winreg'); const yaml = window.require('js-yaml'); const fs = window.require('fs'); const path = window.require('path'); const log = window.require('electron-log'); -import { PowerShell, LauncherAutoClose } from '../paths.js'; class Uplay { - static isInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Uplay' - }); + static isInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Uplay', + }); - reg.valueExists('', (err, exists) => { - if (err) { - reject(new Error('Could not check if Uplay is installed.')); - } - resolve(exists); - }); + reg.valueExists('', (err, exists) => { + if (err) { + reject(new Error('Could not check if Uplay is installed.')); + } + resolve(exists); + }); + }); + } + + static getUplayPath() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Ubisoft\\Launcher', + }); + + reg.values((err, items) => { + if (err) { + reject(err); + } + + let uplayPath = false; + + items.forEach((item) => { + if (item.name === 'InstallDir') { + uplayPath = item.value; + } }); - } - static getUplayPath() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Ubisoft\\Launcher' - }); + if (uplayPath) { + resolve(uplayPath); + } else { + reject(new Error('Could not find Uplay path.')); + } + }); + }); + } - reg.values((err, items) => { - if (err) { - reject(err); - } + static parseConfig(config) { + return new Promise((resolve, reject) => { + const configFile = fs.readFileSync(config, 'hex'); - let uplayPath = false; + const finalOutput = []; + let game = ['root:']; + let launcherId = null; + this._generateHexArr(configFile).forEach((hexStr) => { + const line = Buffer.from(hexStr, 'hex').toString('utf8').replace(/\n/g, ''); + const foundId = hexStr.match(/08([0-9a-f]+)10[0-9a-f]+1a/); + if (foundId) { + if (game.length === 1) { + const hexChars = foundId[1].match(/.{1,2}/g); + const ints = hexChars.map((x) => parseInt(x, 16)); + launcherId = this._convertLaunchId(ints); + return; + } if (game.length > 1) { + try { + const gameParsed = yaml.load(game.join('\n')); - items.forEach((item) => { - if (item.name === 'InstallDir') { - uplayPath = item.value; - } - }); + if (launcherId) { + gameParsed.root.launcher_id = launcherId; + } + finalOutput.push(gameParsed); + } catch (e) { + reject(new Error('Could not parse YAML')); + } - if (uplayPath) { - resolve(uplayPath); - } else { - reject(new Error('Could not find Uplay path.')); - } - }); - }); + const hexChars = foundId[1].match(/.{1,2}/g); + const ints = hexChars.map((x) => parseInt(x, 16)); + launcherId = this._convertLaunchId(ints); + game = ['root:']; + return; + } + } + + // Already manually saved "root:" + if (line.trim().includes('root:') && !line.trim().includes('_')) { + return; + } + + // Save lines if starts with spaces + if (line.substr(0, 2) === ' ' && !line.includes('sort_string:')) { + game.push(line); + } + }); + resolve(finalOutput); + }); + } + + static _generateHexArr(str) { + const lines = []; + const split = str.match(/.{1,2}/g); + let line = ''; + for (let i = 0; i < split.length; i++) { + line += split[i]; + if (split[i] === '0a' && split[i - 2] !== '08') { + lines.push(line); + line = ''; + } } + return lines; + } - static parseConfig(config) { - return new Promise((resolve, reject) => { - const configFile = fs.readFileSync(config, 'hex'); - - const finalOutput = []; - let game = ['root:']; - let launcherId = null; - this._generateHexArr(configFile).forEach((hexStr) => { - const line = Buffer.from(hexStr, 'hex').toString('utf8').replace(/\n/g, ''); - const foundId = hexStr.match(/08([0-9a-f]+)10[0-9a-f]+1a/); - if (foundId) { - if (game.length === 1) { - const hexChars = foundId[1].match(/.{1,2}/g); - const ints = hexChars.map((x) => parseInt(x, 16)); - launcherId = this._convertLaunchId(ints); - return; - } else if (game.length > 1) { - try { - const gameParsed = yaml.load(game.join('\n')); - - if (launcherId) { - gameParsed.root.launcher_id = launcherId; - } - finalOutput.push(gameParsed); - } catch (e) { - reject('Could not parse YAML'); - } - - const hexChars = foundId[1].match(/.{1,2}/g); - const ints = hexChars.map((x) => parseInt(x, 16)); - launcherId = this._convertLaunchId(ints); - game = ['root:']; - return; - } - } + static _processRegKey(key) { + return new Promise((resolve) => { + const id = path.basename(key.key); + key.get('InstallDir', (err, installDir) => { + resolve({ + id, + installDir: installDir.value, + }); + }); + }); + } - // Already manually saved "root:" - if (line.trim().includes('root:') && !line.trim().includes('_')) { - return; - } + static _getRegInstalled() { + return new Promise((resolve, reject) => { + const reg = new Registry({ + hive: Registry.HKLM, + arch: 'x86', + key: '\\SOFTWARE\\Ubisoft\\Launcher\\Installs', + }); + reg.keys((err, keys) => { + if (err) { + reject(err); + } - // Save lines if starts with spaces - if (line.substr(0, 2) == ' ' && !line.includes('sort_string:')) { - game.push(line); - } + if (keys) { + const promiseArr = keys.map((key) => this._processRegKey(key).then((res) => res)); + Promise.all(promiseArr).then((resultsArray) => { + const out = {}; + resultsArray.forEach((item) => { + out[String(item.id)] = item.installDir; }); - resolve(finalOutput); - }); + return resolve(out); + }); + } else { + return resolve({}); + } + return false; + }); + }); + } + + static _convertLaunchId(hexArr) { + let launchId = 0; + let multiplier = 1; + for (let i = 0; i < hexArr.length; i++, multiplier *= 256) { + if (hexArr[i] === 16) { + break; + } + launchId += (hexArr[i] * multiplier); } - static _generateHexArr(str) { - const lines = []; - const split = str.match(/.{1,2}/g); - let line = ''; - for (let i = 0; i < split.length; i++) { - line = line+split[i]; - if (split[i] === '0a' && split[i-2] !== '08') { - lines.push(line); - line = ''; - } - } - return lines; + if (launchId > 256 * 256) { + launchId -= (128 * 256 * Math.ceil(launchId / (256 * 256))); + launchId -= (128 * Math.ceil(launchId / 256)); + } else if (launchId > 256) { + launchId -= (128 * Math.ceil(launchId / 256)); } + return launchId; + } - static _processRegKey(key) { - return new Promise((resolve) => { - const id = path.basename(key.key); - key.get('InstallDir', (err, installDir) => { - resolve({ - id: id, - installDir: installDir.value - }); - }); + static resolveConfigPath(value) { + const hives = { + HKEY_LOCAL_MACHINE: 'HKLM', + HKEY_CURRENT_USER: 'HKCU', + HKEY_CLASSES_ROOT: 'HKCR', + HKEY_USERS: 'HKU', + HKEY_CURRENT_CONFIG: 'HKCC', + }; + let output = ''; + return new Promise((resolve) => { + // Value is stored in registry + if (value.register) { + const key = value.register.split('\\'); + const hive = hives[key.shift()]; + const valueName = key.pop(); + const reg = new Registry({ + hive, + arch: 'x86', + key: `\\${key.join('\\')}`, }); - } + reg.values((err, items) => { + if (err) { + return resolve(false); + } + items.forEach((item) => { + if (item.name.toLowerCase() === valueName.toLowerCase()) { + output += item.value; + if (value.append) { + output += value.append; + } + return resolve(output); + } + return false; + }); + return false; + }); + } else if (value.relative) { + return resolve(value.relative); + } + }); + } - static _getRegInstalled() { - return new Promise((resolve, reject) => { - const reg = new Registry({ - hive: Registry.HKLM, - arch: 'x86', - key: '\\SOFTWARE\\Ubisoft\\Launcher\\Installs' - }); - reg.keys((err, keys) => { - if (err) { - reject(err); - } + static getGameExes(executables, workingDirFallback = false) { + return new Promise((resolve) => { + const promises = []; + executables.forEach((exe) => { + const promise = new Promise((resolveExe) => { + let append = ''; + if (exe.working_directory.append) { + append = exe.working_directory.append; + } + this.resolveConfigPath(exe.path).then((exePath) => { + if (!exePath) { + resolveExe(false); + } - if (keys) { - const promiseArr = keys.map((key) => this._processRegKey(key).then((res) => res)); - Promise.all(promiseArr).then((resultsArray) => { - const out = {}; - resultsArray.forEach((item) => { - out[String(item.id)] = item.installDir; - }); - return resolve(out); - }); + // Get working directory + this.resolveConfigPath(exe.working_directory).then((workingDir) => { + if (workingDir) { + // check if exe is actually there + if (fs.existsSync(path.join(workingDir, append, exePath))) { + resolveExe(path.join(workingDir, append, exePath)); } else { - return resolve({}); + resolveExe(false); } + } else if (workingDirFallback && fs.existsSync(path.join(workingDirFallback, exePath))) { + resolveExe(path.join(workingDirFallback, exePath)); + } else if (workingDirFallback && fs.existsSync(path.join(workingDirFallback, append, exePath))) { + resolveExe(path.join(workingDirFallback, append, exePath)); + } else { + resolveExe(false); + } }); + }); }); - } + promises.push(promise); + }); + Promise.all(promises).then((results) => resolve(results)); + }); + } - static _convertLaunchId(hexArr) { - let launchId = 0; - let multiplier = 1; - for (let i = 0; i < hexArr.length; i++, multiplier = multiplier*256) { - if (hexArr[i] === 16) { - break; - } - launchId = launchId + (hexArr[i] * multiplier); - } + static getGames() { + return new Promise((resolve, reject) => { + log.info('Import: Started uplay'); - if (launchId > 256 * 256) { - launchId = launchId - (128 * 256 * Math.ceil(launchId / (256 * 256))); - launchId = launchId - (128 * Math.ceil(launchId / 256)); - } else if (launchId > 256) { - launchId = launchId - (128 * Math.ceil(launchId / 256)); - } - return launchId; - } + this.getUplayPath().then((uplayPath) => { + this.parseConfig(path.join(uplayPath, 'cache', 'configuration', 'configurations')).then((configItems) => { + this._getRegInstalled().then((installedGames) => { + // Only need launch IDs + const installedGamesIds = Object.keys(installedGames); - static resolveConfigPath(value) { - const hives = { - 'HKEY_LOCAL_MACHINE': 'HKLM', - 'HKEY_CURRENT_USER': 'HKCU', - 'HKEY_CLASSES_ROOT': 'HKCR', - 'HKEY_USERS': 'HKU', - 'HKEY_CURRENT_CONFIG': 'HKCC', - }; - let output = ''; - return new Promise((resolve) => { - // Value is stored in registry - if (value.register) { - const key = value.register.split('\\'); - const hive = hives[key.shift()]; - const valueName = key.pop(); - const reg = new Registry({ - hive: hive, - arch: 'x86', - key: `\\${key.join('\\')}` - }); - reg.values((err, items) => { - if (err) { - return resolve(false); - } - items.forEach((item) => { - if (item.name.toLowerCase() === valueName.toLowerCase()) { - output += item.value; - if (value.append) { - output += value.append; - } - return resolve(output); - } - }); - }); - } else if (value.relative) { - return resolve(value.relative); - } - }); - } + const games = []; + const addGamesPromises = []; + const invalidNames = ['NAME', 'GAMENAME', 'l1']; + configItems.forEach((game) => { + if (game.root.start_game) { // DLC's and other non-games dont have this key + // Skip adding games launched via Steam. + if (game.root.start_game.steam) { + return; + } + + let gameName = game.root.name; + let gameId; + + // Get name from another key if has weird name assigned + if (invalidNames.includes(game.root.name)) { + if (typeof game.root.installer !== 'undefined') { + gameName = game.root.installer.game_identifier; + } + + // Override installer name if this value + if (typeof game.root.default[game.root.name] !== 'undefined') { + gameName = game.root.default[game.root.name]; + } + } - static getGameExes(executables, workingDirFallback = false) { - return new Promise((resolve) => { - const promises = []; - executables.forEach((exe) => { - const promise = new Promise((resolve) => { - let append = ''; - if (exe.working_directory.append) { - append = exe.working_directory.append; - } - this.resolveConfigPath(exe.path).then((exePath) => { - if (!exePath) { - resolve(false); - } - - // Get working directory - this.resolveConfigPath(exe.working_directory).then((workingDir) => { - if (workingDir) { - // check if exe is actually there - if (fs.existsSync(path.join(workingDir, append, exePath))) { - resolve(path.join(workingDir, append, exePath)); - } else { - resolve(false); - } - } else { - if (workingDirFallback && fs.existsSync(path.join(workingDirFallback, exePath))) { - resolve(path.join(workingDirFallback, exePath)); - } else if (workingDirFallback && fs.existsSync(path.join(workingDirFallback, append, exePath))) { - resolve(path.join(workingDirFallback, append, exePath)); - } else { - resolve(false); - } - } + if (game.root.space_id) { + gameId = game.root.space_id; + } else { + // No space_id means legacy game. Use launch id as ID. + gameId = game.root.launcher_id; + } + + // Only add if launcher id is found in registry and has executables + if (installedGamesIds.includes(String(game.root.launcher_id)) && (game.root.start_game.offline || game.root.start_game.online)) { + const addGame = this.getGameExes((game.root.start_game.offline || game.root.start_game.online).executables, installedGames[game.root.launcher_id]) + .then((executables) => { + if (executables.every((x) => x !== false)) { + const watchedExes = executables.map((x) => path.parse(path.basename(x)).name); + games.push({ + id: gameId, + name: gameName, + exe: `"${PowerShell}"`, + icon: `"${executables[0]}"`, + startIn: `"${uplayPath}"`, + params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\" -launcher \\"upc\\" -game \\"${watchedExes.join('\\",\\"')}\\" -launchcmd \\"uplay://launch/${game.root.launcher_id}\\""`, + platform: 'uplay', }); + } else { + log.info(`Import: uplay - Could not resolve executable for ${gameName}`); + } }); - }); - promises.push(promise); + addGamesPromises.push(addGame); + } + } }); - Promise.all(promises).then((results) => resolve(results)); - }); - } - - static getGames() { - return new Promise((resolve, reject) => { - log.info('Import: Started uplay'); - - this.getUplayPath().then((uplayPath) => { - this.parseConfig(path.join(uplayPath, 'cache', 'configuration', 'configurations')).then((configItems) => { - this._getRegInstalled().then((installedGames) => { - // Only need launch IDs - const installedGamesIds = Object.keys(installedGames); - - const games = []; - const addGamesPromises = []; - const invalidNames = ['NAME', 'GAMENAME', 'l1']; - configItems.forEach((game) => { - if (game.root.start_game) { // DLC's and other non-games dont have this key - // Skip adding games launched via Steam. - if (game.root.start_game.steam) { - return; - } - - let gameName = game.root.name; - let gameId; - - // Get name from another key if has weird name assigned - if (invalidNames.includes(game.root.name)) { - if (typeof game.root.installer !== 'undefined') { - gameName = game.root.installer.game_identifier; - } - - // Override installer name if this value - if (typeof game.root.default[game.root.name] !== 'undefined') { - gameName = game.root.default[game.root.name]; - } - } - - if (game.root.space_id) { - gameId = game.root.space_id; - } else { - // No space_id means legacy game. Use launch id as ID. - gameId = game.root.launcher_id; - } - - // Only add if launcher id is found in registry and has executables - if (installedGamesIds.includes(String(game.root.launcher_id)) && (game.root.start_game.offline || game.root.start_game.online)) { - const addGame = this.getGameExes((game.root.start_game.offline || game.root.start_game.online).executables, installedGames[game.root.launcher_id]) - .then((executables) => { - if (executables.every((x) => x !== false)) { - const watchedExes = executables.map((x) => path.parse(path.basename(x)).name); - games.push({ - id: gameId, - name: gameName, - exe: `"${PowerShell}"`, - icon: `"${executables[0]}"`, - startIn: `"${uplayPath}"`, - params: `-windowstyle hidden -NoProfile -ExecutionPolicy Bypass -Command "& \\"${LauncherAutoClose}\\" -launcher \\"upc\\" -game \\"${watchedExes.join('\\",\\"')}\\" -launchcmd \\"uplay://launch/${game.root.launcher_id}\\""`, - platform: 'uplay' - }); - } else { - log.info(`Import: uplay - Could not resolve executable for ${gameName}`); - } - }); - addGamesPromises.push(addGame); - } - } - }); - Promise.all(addGamesPromises).then(() => { - log.info('Import: Completed uplay'); - return resolve(games); - }); - }).catch((err) => reject(err)); - }).catch((err) => reject(err)); - }).catch((err) => reject(err)); - }); - } + Promise.all(addGamesPromises).then(() => { + log.info('Import: Completed uplay'); + return resolve(games); + }); + }).catch((err) => reject(err)); + }).catch((err) => reject(err)); + }).catch((err) => reject(err)); + }); + } } export default Uplay; export const name = 'Uplay'; export const id = 'uplay'; -export const official = true; \ No newline at end of file +export const official = true; diff --git a/src/js/index.js b/src/js/index.jsx similarity index 81% rename from src/js/index.js rename to src/js/index.jsx index dc71dec..b597a44 100644 --- a/src/js/index.js +++ b/src/js/index.jsx @@ -1,5 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import App from './App.js'; +import App from './App'; ReactDOM.render(, document.getElementById('root')); diff --git a/src/js/paths.js b/src/js/paths.js index da308b6..d7dc670 100644 --- a/src/js/paths.js +++ b/src/js/paths.js @@ -1,9 +1,9 @@ const fs = window.require('fs'); const path = window.require('path'); -let launcherWatcher = path.resolve(path.dirname(process.resourcesPath), '../../', 'LauncherAutoClose.ps1'); +let launcherWatcher = path.resolve(path.dirname(process.resourcesPath), '../../../', 'LauncherAutoClose.ps1'); if (!fs.existsSync(launcherWatcher)) { - launcherWatcher = path.join(path.dirname(process.resourcesPath), 'LauncherAutoClose.ps1'); + launcherWatcher = path.join(path.dirname(process.resourcesPath), 'LauncherAutoClose.ps1'); } export const PowerShell = path.join(process.env.windir, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'); diff --git a/src/js/spinner.js b/src/js/spinner.js deleted file mode 100644 index 3935c74..0000000 --- a/src/js/spinner.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {ProgressCircle} from 'react-desktop/windows'; - -class Spinner extends React.Component { - constructor(props) { - super(props); - } - - render() { - return ( -
- -
- ); - } -} - -Spinner.contextTypes = { theme: PropTypes.object }; -export default Spinner; diff --git a/src/js/toastHandler.js b/src/js/toastHandler.js deleted file mode 100644 index 7708c2d..0000000 --- a/src/js/toastHandler.js +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import PubSub from 'pubsub-js'; -import Toast from 'react-uwp/Toast'; -import Icon from 'react-uwp/Icon'; - -class ToastHandler extends React.Component { - constructor(props) { - super(props); - this.close = this.close; - this.state = { - toasts: [] - }; - } - - componentDidMount() { - PubSub.subscribe('toast', (message, args) => { - const toast = {toast: args, show: true}; - this.close(toast, 3000); - this.setState({ - toasts: this.state.toasts.concat(toast) - }); - }); - } - - close(toast, closeDelay) { - const self = this; - setTimeout(() => { - const toasts = self.state.toasts.slice(0); - toasts[toasts.indexOf(toast)].show = false; - self.setState({ - toasts: toasts - }); - }, closeDelay); - } - - render() { - const toasts = this.state.toasts.slice(0).map((x, i) => ( - {x.toast.logoNode}} - title={x.toast.title} - showCloseIcon - > - {x.toast.contents} - - )); - return toasts; - } -} - -export default ToastHandler; \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 480931a..d5241dd 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,38 +1,38 @@ const path = require('path'); module.exports = { - entry: './src/js/index.js', - mode: 'development', - devtool: 'source-map', - target: 'node', - module: { - rules: [ - { - test: /\.(js|jsx)$/, - exclude: /(node_modules|bower_components)/, - loader: 'babel-loader', - options: { presets: ['@babel/env'] } - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(png|svg|jpg|gif)$/, - use: { - loader: 'file-loader', - options: { - outputPath: 'img', - publicPath: './img' - } - } - } - ] - }, - resolve: { extensions: ['*', '.js', '.jsx'] }, - output: { - path: path.resolve(__dirname, 'public/'), - publicPath: '/public/', - filename: 'bundle.js' - } + entry: './src/js/index.jsx', + mode: 'development', + devtool: 'source-map', + target: 'node', + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel-loader', + options: { presets: ['@babel/env'] }, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.(png|svg|jpg|gif)$/, + use: { + loader: 'file-loader', + options: { + outputPath: 'img', + publicPath: './img', + }, + }, + }, + ], + }, + resolve: { extensions: ['*', '.js', '.jsx'] }, + output: { + path: path.resolve(__dirname, 'public/'), + publicPath: '/public/', + filename: 'bundle.js', + }, };