diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..eb1908282 --- /dev/null +++ b/.npmrc @@ -0,0 +1,5 @@ +# pnpm-related options +shamefully-hoist=true +strict-peer-dependencies=false +# to get the latest compatible packages when creating the project https://github.com/pnpm/pnpm/issues/6463 +resolution-mode=highest diff --git a/.postcssrc.js b/.postcssrc.js deleted file mode 100644 index 1174fe52b..000000000 --- a/.postcssrc.js +++ /dev/null @@ -1,8 +0,0 @@ -// https://github.com/michael-ciniawsky/postcss-load-config - -module.exports = { - plugins: [ - // to edit target browsers: use "browserslist" field in package.json - require('autoprefixer') - ] -} diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 90ff9bb4b..000000000 --- a/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "semi": true -} \ No newline at end of file diff --git a/.space.kts b/.space.kts deleted file mode 100644 index e52385f37..000000000 --- a/.space.kts +++ /dev/null @@ -1,20 +0,0 @@ -/** -* JetBrains Space Automation -* This Kotlin-script file lets you automate build activities -* For more info, see https://www.jetbrains.com/help/space/automation.html -*/ - -job("Run npm test and publish") { - container(displayName = "Run tests", image = "node:14-alpine") { - env["REGISTRY"] = "https://registry.npmjs.org" - shellScript { - interpreter = "/bin/sh" - content = """ - echo Install npm dependencies... - npm install - echo Run tests... - npm run test - """ - } - } -} diff --git a/.stylintrc b/.stylintrc deleted file mode 100644 index de984f6fd..000000000 --- a/.stylintrc +++ /dev/null @@ -1,35 +0,0 @@ -{ - "blocks": "never", - "brackets": "never", - "colons": "never", - "colors": "always", - "commaSpace": "always", - "commentSpace": "always", - "cssLiteral": "never", - "depthLimit": false, - "duplicates": true, - "efficient": "always", - "extendPref": false, - "globalDupe": true, - "indentPref": 4, - "leadingZero": "never", - "maxErrors": false, - "maxWarnings": false, - "mixed": false, - "namingConvention": false, - "namingConventionStrict": false, - "none": "never", - "noImportant": false, - "parenSpace": "never", - "placeholder": false, - "prefixVarsWithDollar": "always", - "quotePref": "single", - "semicolons": "always", - "sortOrder": false, - "stackedProperties": "never", - "trailingWhitespace": "never", - "universal": "never", - "valid": true, - "zeroUnits": "never", - "zIndexNormalize": false -} diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 6d506e0af..000000000 --- a/babel.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/* eslint-env node */ -// eslint-disable-next-line @typescript-eslint/no-var-requires - -module.exports = { - presets: ['@quasar/babel-preset-app'] -}; diff --git a/index.html b/index.html new file mode 100644 index 000000000..3c8c78f00 --- /dev/null +++ b/index.html @@ -0,0 +1,21 @@ + + + + <%= productName %> + + + + + + + + + + + + + + + + + diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 54c9b0155..000000000 --- a/jest.config.js +++ /dev/null @@ -1,82 +0,0 @@ -const esModules = ['quasar/lang', 'lodash-es'].join('|'); - -/* eslint-env node */ -module.exports = { - globals: { - __DEV__: true, - // TODO: Remove if resolved natively https://github.com/vuejs/vue-jest/issues/175 - 'vue-jest': { - pug: { doctype: 'html' } - } - }, - testEnvironmentOptions: { - url: 'http://localhost/', - }, - testEnvironment: 'jsdom', - testURL: 'http://localhost/', - setupFilesAfterEnv: ['/test/jest/jest.setup.ts'], - // noStackTrace: true, - // bail: true, - // cache: false, - // verbose: true, - // watch: true, - collectCoverage: false, - coverageDirectory: '/test/jest/coverage', - collectCoverageFrom: [ - '/src/**/*.vue', - '/src/**/*.js', - '/src/**/*.ts', - '/src/**/*.jsx', - '/src/**/*.tsx' - ], - coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'], - coverageThreshold: { - global: { - // branches: 50, - // functions: 50, - // lines: 50, - // statements: 50 - } - }, - testMatch: [ - // Matches tests in any subfolder of 'src' or into 'test/jest/__tests__' - // Matches all files with extension 'js', 'jsx', 'ts' and 'tsx' - '/test/jest/__tests__/**/*.(spec|test).+(ts|js)?(x)', - '/src/**/*.jest.(spec|test).+(ts|js)?(x)' - ], - // Extension-less imports of components are resolved to .ts files by TS, - // grating correct type-checking in test files. - // Being 'vue' the first moduleFileExtension option, the very same imports - // will be resolved to .vue files by Jest, if both .vue and .ts files are - // in the same folder. - // This guarantee a great dev experience both for testing and type-checking. - // See https://github.com/vuejs/vue-jest/issues/188#issuecomment-620750728 - moduleFileExtensions: ['vue', 'js', 'jsx', 'json', 'ts', 'tsx'], - moduleNameMapper: { - '^quasar$': 'quasar/dist/quasar.common.js', - '^~/(.*)$': '/$1', - '^src/(.*)$': '/src/$1', - '^app/(.*)$': '/$1', - '^components/(.*)$': '/src/components/$1', - '^layouts/(.*)$': '/src/layouts/$1', - '^pages/(.*)$': '/src/pages/$1', - '^assets/(.*)$': '/src/assets/$1', - '^boot/(.*)$': '/src/boot/$1', - '.*css$': '/test/jest/__tests__/__stub_module_files__/style.js' - }, - transform: { - // See https://jestjs.io/docs/en/configuration.html#transformignorepatterns-array-string - [`^(${esModules}).+\\.js$`]: 'babel-jest', - '^.+\\.(ts|js|html)$': 'ts-jest', - '^.+\\.(ts|js)$': 'babel-jest', - // vue-jest uses find-babel-file, which searches by this order: - // (async) .babelrc, .babelrc.js, package.json, babel.config.js - // (sync) .babelrc, .babelrc.js, babel.config.js, package.json - // https://github.com/tleunen/find-babel-config/issues/33 - '.*\\.vue$': 'vue-jest', - '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': - 'jest-transform-stub' - }, - transformIgnorePatterns: [`node_modules/(?!(${esModules}))`], - snapshotSerializers: ['/node_modules/jest-serializer-vue'] -}; diff --git a/package.json b/package.json index 1733ad7cf..2bfb9b0fe 100644 --- a/package.json +++ b/package.json @@ -1,139 +1,125 @@ { - "name": "r2modman", - "version": "3.2.3", - "description": "A simple and easy to use mod manager for many games using Thunderstore.", - "productName": "r2modman", - "author": "ebkr", - "private": true, - "license": "MIT", - "scripts": { - "test": "echo \"See package.json => scripts for available tests.\" && exit 0", - "run": "quasar dev -m electron --modern", - "sync": "ts-node ./scripts/sync.ts", - "build-win": "quasar build --mode electron -T win32", - "build-linux": "quasar build --mode electron -T linux", - "build-osx": "quasar build --mode electron -T mac", - "publish": "quasar build --mode electron --publish always", - "publish-win": "quasar build --mode electron -T win32 --publish always", - "publish-linux": "quasar build --mode electron -T linux --publish always", - "test:unit": "jest --updateSnapshot", - "test:unit:ci": "jest --ci", - "test:unit:coverage": "jest --coverage", - "test:unit:watch": "jest --watch", - "test:unit:watchAll": "jest --watchAll", - "serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788", - "concurrently:dev:jest": "concurrently \"quasar dev -m electron --modern\" \"jest --watch\"", - "test:unit:ui": "majestic" - }, - "dependencies": { - "@node-steam/vdf": "^2.1.0", - "@quasar/extras": "^1.0.0", - "adm-zip": "^0.5.5", - "ajv": "^8.17.1", - "ajv-formats": "^3.0.1", - "async-lock": "^1.2.6", - "axios": "^0.24.0", - "bulma": "^0.9.4", - "bulma-checkradio": "^1.1.1", - "bulma-divider": "^0.2.0", - "bulma-slider": "2.0.4", - "bulma-steps": "^2.2.1", - "bulma-switch": "^2.0.0", - "core-js": "^3.6.5", - "dexie": "^3.2.7", - "dot-prop": "^5.2.0", - "electron-updater": "4.2.5", - "elliptic": "^6.5.4", - "fflate": "^0.8.2", - "floating-vue": "^1.0.0-beta.19", - "fs-extra": "^8.1.0", - "github-markdown-css": "^5.7.0", - "glob-parent": "^6.0.2", - "highlight.js": "^10.4.1", - "lodash.debounce": "^4.0.8", - "modern-normalize": "^3.0.1", - "moment": "^2.29.1", - "node-ipc": "^9.1.1", - "quasar": "^1.14.7", - "quill": "^1.3.7", - "sanitize-filename": "^1.6.3", - "serialize-javascript": "^3.1.0", - "tar": "^6.1.11", - "trim-newlines": "^4.0.2", - "unzipper": "^0.10.5", - "uuid-js": "^0.7.5", - "vue-i18n": "^8.0.0", - "vuedraggable": "^2.24.3", - "yaml": "^1.7.2" - }, - "devDependencies": { - "@babel/core": "^7.4.0", - "@babel/preset-typescript": "^7.14.5", - "@quasar/app": "^2.0.9", - "@quasar/quasar-app-extension-testing": "^1.0.3", - "@quasar/quasar-app-extension-testing-unit-jest": "^2.2.2", - "@quasar/quasar-app-extension-typescript": "^1.0.2", - "@types/adm-zip": "^0.4.34", - "@types/async-lock": "^1.1.2", - "@types/chai": "^4.2.11", - "@types/fs-extra": "^8.0.1", - "@types/jsdom": "^21.1.7", - "@types/lodash.debounce": "^4.0.7", - "@types/node": "^12.12.12", - "@types/quill": "^2.0.3", - "@types/sinon": "^10.0.2", - "@types/unzipper": "^0.10.1", - "@types/yaml": "^1.2.0", - "@typescript-eslint/eslint-plugin": "^1.12.0", - "@typescript-eslint/parser": "^1.12.0", - "@vue/eslint-config-airbnb": "^4.0.0", - "@vue/test-utils": "^1.3.6", - "babel-core": "^7.0.0-beta.3", - "babel-eslint": "^10.0.1", - "babel-jest": "^27.0.2", - "chai": "^4.2.0", - "devtron": "^1.4.0", - "electron": "^24.0.0", - "electron-builder": "^24.10.5", - "electron-debug": "^3.0.1", - "electron-devtools-installer": "^3.0.0", - "electron-packager": "14.1.1", - "eslint": "^6.8.0", - "eslint-config-prettier": "^6.0.0", - "eslint-loader": "^2.1.1", - "eslint-plugin-jest": "^24.1.0", - "eslint-plugin-vue": "^5.0.0", - "identity-obj-proxy": "^3.0.0", - "jest": "^27.5.1", - "jsdom": "^25.0.1", - "majestic": "^1.2.24", - "minimist": "^1.2.2", - "mock-require": "^3.0.3", - "quicktype-core": "^23.0.171", - "sass": "^1.70.0", - "sass-loader": "^10.2.1", - "sinon": "^11.1.1", - "ts-jest": "^29.2.5", - "ts-node": "^8.10.2", - "typescript": "^4.5.5", - "vue": "2.7.16", - "vue-jest": "^3.0.0", - "wallaby-vue-compiler": "^1.0.3" - }, - "browserslist": [ - "last 10 Chrome versions", - "last 10 Firefox versions", - "last 4 Edge versions", - "last 7 Safari versions", - "last 8 Android versions", - "last 8 ChromeAndroid versions", - "last 8 FirefoxAndroid versions", - "last 10 iOS versions", - "last 5 Opera versions" - ], - "engines": { - "node": ">= 18.0.0", - "npm": ">= 10.0.0", - "yarn": ">= 1.21.1" - } + "name": "r2modman", + "version": "3.2.1", + "description": "A simple and easy to use mod manager for many games using Thunderstore.", + "productName": "r2modman", + "author": "ebkr", + "private": true, + "license": "MIT", + "type": "module", + "scripts": { + "test": "echo \"See package.json => scripts for available tests.\" && exit 0", + "run": "quasar dev -m electron --modern", + "dev": "quasar dev -m electron --modern --devtools", + "sync": "ts-node ./scripts/sync.ts", + "build-win": "quasar build --mode electron -T win32", + "build-linux": "quasar build --mode electron -T linux", + "build-osx": "quasar build --mode electron -T mac", + "publish": "quasar build --mode electron --publish always", + "publish-win": "quasar build --mode electron -T win32 --publish always", + "publish-linux": "quasar build --mode electron -T linux --publish always", + "test:unit": "jest", + "test:unit:ci": "jest --ci", + "test:unit:coverage": "jest --coverage", + "test:unit:watch": "jest --watch", + "test:unit:watchAll": "jest --watchAll", + "serve:test:coverage": "quasar serve test/jest/coverage/lcov-report/ --port 8788", + "concurrently:dev:jest": "concurrently \"quasar dev -m electron --modern\" \"jest --watch\"", + "test:unit:ui": "majestic" + }, + "dependencies": { + "@node-steam/vdf": "^2.1.0", + "@quasar/extras": "^1.17.0", + "adm-zip": "^0.5.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "async-lock": "^1.2.6", + "axios": "^0.24.0", + "bulma": "^0.9.4", + "bulma-checkradio": "^1.1.1", + "bulma-divider": "^0.2.0", + "bulma-slider": "2.0.4", + "bulma-steps": "^2.2.1", + "bulma-switch": "^2.0.0", + "core-js": "^3.6.5", + "dexie": "^3.2.7", + "dot-prop": "^5.2.0", + "electron-updater": "4.2.5", + "elliptic": "^6.5.4", + "fflate": "^0.8.2", + "floating-vue": "5.2.2", + "fs-extra": "^8.1.0", + "github-markdown-css": "^5.7.0", + "glob-parent": "^6.0.2", + "highlight.js": "^10.4.1", + "lodash.debounce": "^4.0.8", + "modern-normalize": "^3.0.1", + "moment": "^2.29.1", + "node-ipc": "^12.0.0", + "quasar": "^2.18.1", + "quill": "^1.3.7", + "sanitize-filename": "^1.6.3", + "serialize-javascript": "^3.1.0", + "tar": "^6.1.11", + "trim-newlines": "^4.0.2", + "unzipper": "^0.10.5", + "uuid-js": "^0.7.5", + "vue-i18n": "^11.1.6", + "vue-router": "^4.5.1", + "vuedraggable": "4.1.0", + "vuex": "^4.1.0", + "yaml": "^1.7.2" + }, + "devDependencies": { + "@babel/core": "^7.4.0", + "@babel/preset-typescript": "^7.14.5", + "@quasar/app-vite": "^2.2.1", + "@types/adm-zip": "^0.4.34", + "@types/async-lock": "^1.1.2", + "@types/chai": "^4.2.11", + "@types/fs-extra": "^8.0.1", + "@types/jsdom": "^21.1.7", + "@types/lodash.debounce": "^4.0.7", + "@types/node": "^20.19.1", + "@types/quill": "^2.0.3", + "@types/sinon": "^10.0.2", + "@types/unzipper": "^0.10.1", + "@types/yaml": "^1.2.0", + "@vue/devtools": "^7.7.7", + "@vue/test-utils": "^2.4.6", + "autoprefixer": "^10.4.21", + "babel-core": "^7.0.0-beta.3", + "babel-jest": "^27.0.2", + "chai": "^4.2.0", + "devtron": "^1.4.0", + "electron": "^36.5.0", + "electron-builder": "^24.10.5", + "electron-debug": "^3.0.1", + "electron-devtools-installer": "^3.0.0", + "electron-packager": "14.1.1", + "eslint-plugin-jest": "^28.8.3", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.7.0", + "jsdom": "^25.0.1", + "majestic": "^1.2.24", + "minimist": "^1.2.2", + "mock-require": "^3.0.3", + "quicktype-core": "^23.0.171", + "postcss": "^8.5.6", + "sass": "^1.70.0", + "sass-loader": "^16.0.5", + "sinon": "^11.1.1", + "ts-jest": "^29.2.5", + "ts-node": "^8.10.2", + "typescript": "^5.9.2", + "vite-plugin-node-polyfills": "^0.23.0", + "vue": "^3.5.16", + "vue-jest": "^3.0.0", + "wallaby-vue-compiler": "^1.0.3" + }, + "engines": { + "node": ">= 18.0.0", + "npm": ">= 10.0.0", + "yarn": ">= 1.21.1" + } } diff --git a/public/images/game_selection/20MinutesTillDawn.jpg b/public/images/game_selection/20MinutesTillDawn.jpg new file mode 100644 index 000000000..f118d4c77 Binary files /dev/null and b/public/images/game_selection/20MinutesTillDawn.jpg differ diff --git a/public/images/game_selection/9Kings.png b/public/images/game_selection/9Kings.png new file mode 100644 index 000000000..3549b115c Binary files /dev/null and b/public/images/game_selection/9Kings.png differ diff --git a/public/images/game_selection/AGAINST.jpg b/public/images/game_selection/AGAINST.jpg new file mode 100644 index 000000000..4e2a2955b Binary files /dev/null and b/public/images/game_selection/AGAINST.jpg differ diff --git a/public/images/game_selection/ANEURISM_IV.png b/public/images/game_selection/ANEURISM_IV.png new file mode 100644 index 000000000..de8fd76b4 Binary files /dev/null and b/public/images/game_selection/ANEURISM_IV.png differ diff --git a/public/images/game_selection/ASKA.png b/public/images/game_selection/ASKA.png new file mode 100644 index 000000000..95efd43af Binary files /dev/null and b/public/images/game_selection/ASKA.png differ diff --git a/public/images/game_selection/ATLYSS.png b/public/images/game_selection/ATLYSS.png new file mode 100644 index 000000000..623a1bf82 Binary files /dev/null and b/public/images/game_selection/ATLYSS.png differ diff --git a/public/images/game_selection/AcrossTheObelisk.png b/public/images/game_selection/AcrossTheObelisk.png new file mode 100644 index 000000000..ecd720a4f Binary files /dev/null and b/public/images/game_selection/AcrossTheObelisk.png differ diff --git a/public/images/game_selection/AgainstTheStorm.png b/public/images/game_selection/AgainstTheStorm.png new file mode 100644 index 000000000..844147a29 Binary files /dev/null and b/public/images/game_selection/AgainstTheStorm.png differ diff --git a/public/images/game_selection/AleAndTaleTavern.png b/public/images/game_selection/AleAndTaleTavern.png new file mode 100644 index 000000000..aa96626c4 Binary files /dev/null and b/public/images/game_selection/AleAndTaleTavern.png differ diff --git a/public/images/game_selection/Aloft.png b/public/images/game_selection/Aloft.png new file mode 100644 index 000000000..f329c5119 Binary files /dev/null and b/public/images/game_selection/Aloft.png differ diff --git a/public/images/game_selection/AmongUs.png b/public/images/game_selection/AmongUs.png new file mode 100644 index 000000000..de5280353 Binary files /dev/null and b/public/images/game_selection/AmongUs.png differ diff --git a/public/images/game_selection/AnotherCrabsTreasure.png b/public/images/game_selection/AnotherCrabsTreasure.png new file mode 100644 index 000000000..0d39d486c Binary files /dev/null and b/public/images/game_selection/AnotherCrabsTreasure.png differ diff --git a/public/images/game_selection/ArcusChroma.png b/public/images/game_selection/ArcusChroma.png new file mode 100644 index 000000000..6ed1a8a43 Binary files /dev/null and b/public/images/game_selection/ArcusChroma.png differ diff --git a/public/images/game_selection/Atomicrops.jpg b/public/images/game_selection/Atomicrops.jpg new file mode 100644 index 000000000..9c205ae05 Binary files /dev/null and b/public/images/game_selection/Atomicrops.jpg differ diff --git a/public/images/game_selection/BONELAB.jpg b/public/images/game_selection/BONELAB.jpg new file mode 100644 index 000000000..3c7c2ccc7 Binary files /dev/null and b/public/images/game_selection/BONELAB.jpg differ diff --git a/public/images/game_selection/BONEWORKS.jpg b/public/images/game_selection/BONEWORKS.jpg new file mode 100644 index 000000000..62c35f4ea Binary files /dev/null and b/public/images/game_selection/BONEWORKS.jpg differ diff --git a/public/images/game_selection/BackToTheDawn.png b/public/images/game_selection/BackToTheDawn.png new file mode 100644 index 000000000..3b008e814 Binary files /dev/null and b/public/images/game_selection/BackToTheDawn.png differ diff --git a/public/images/game_selection/BackpackHero.jpg b/public/images/game_selection/BackpackHero.jpg new file mode 100644 index 000000000..0a9bcf5d7 Binary files /dev/null and b/public/images/game_selection/BackpackHero.jpg differ diff --git a/public/images/game_selection/Balatro.png b/public/images/game_selection/Balatro.png new file mode 100644 index 000000000..f00e53422 Binary files /dev/null and b/public/images/game_selection/Balatro.png differ diff --git a/public/images/game_selection/BelowTheStone.png b/public/images/game_selection/BelowTheStone.png new file mode 100644 index 000000000..c9631fc7a Binary files /dev/null and b/public/images/game_selection/BelowTheStone.png differ diff --git a/public/images/game_selection/BetrayalBeach.png b/public/images/game_selection/BetrayalBeach.png new file mode 100644 index 000000000..e4fce24c2 Binary files /dev/null and b/public/images/game_selection/BetrayalBeach.png differ diff --git a/public/images/game_selection/BombRushCyberfunk.jpg b/public/images/game_selection/BombRushCyberfunk.jpg new file mode 100644 index 000000000..6030195bc Binary files /dev/null and b/public/images/game_selection/BombRushCyberfunk.jpg differ diff --git a/public/images/game_selection/BoplBattle.png b/public/images/game_selection/BoplBattle.png new file mode 100644 index 000000000..ee300f2f7 Binary files /dev/null and b/public/images/game_selection/BoplBattle.png differ diff --git a/public/images/game_selection/CastleStory.png b/public/images/game_selection/CastleStory.png new file mode 100644 index 000000000..6ab666841 Binary files /dev/null and b/public/images/game_selection/CastleStory.png differ diff --git a/public/images/game_selection/CatsAreLiquidABP.jpg b/public/images/game_selection/CatsAreLiquidABP.jpg new file mode 100644 index 000000000..9e1dcf3b6 Binary files /dev/null and b/public/images/game_selection/CatsAreLiquidABP.jpg differ diff --git a/public/images/game_selection/ChronoArk.jpg b/public/images/game_selection/ChronoArk.jpg new file mode 100644 index 000000000..7119d531a Binary files /dev/null and b/public/images/game_selection/ChronoArk.jpg differ diff --git a/public/images/game_selection/CitiesSkylines2.png b/public/images/game_selection/CitiesSkylines2.png new file mode 100644 index 000000000..62ea9346d Binary files /dev/null and b/public/images/game_selection/CitiesSkylines2.png differ diff --git a/public/images/game_selection/ContentWarning.png b/public/images/game_selection/ContentWarning.png new file mode 100644 index 000000000..8ba40ba62 Binary files /dev/null and b/public/images/game_selection/ContentWarning.png differ diff --git a/public/images/game_selection/CoreKeeper.png b/public/images/game_selection/CoreKeeper.png new file mode 100644 index 000000000..27fecf255 Binary files /dev/null and b/public/images/game_selection/CoreKeeper.png differ diff --git a/public/images/game_selection/Cotl.jpg b/public/images/game_selection/Cotl.jpg new file mode 100644 index 000000000..973e8bfc0 Binary files /dev/null and b/public/images/game_selection/Cotl.jpg differ diff --git a/public/images/game_selection/DEPO.png b/public/images/game_selection/DEPO.png new file mode 100644 index 000000000..92b612498 Binary files /dev/null and b/public/images/game_selection/DEPO.png differ diff --git a/public/images/game_selection/DUSK.png b/public/images/game_selection/DUSK.png new file mode 100644 index 000000000..a761407ee Binary files /dev/null and b/public/images/game_selection/DUSK.png differ diff --git a/public/images/game_selection/DaleAndDawson.png b/public/images/game_selection/DaleAndDawson.png new file mode 100644 index 000000000..ed02047aa Binary files /dev/null and b/public/images/game_selection/DaleAndDawson.png differ diff --git a/public/images/game_selection/DeepRockGalacticSurvivor.png b/public/images/game_selection/DeepRockGalacticSurvivor.png new file mode 100644 index 000000000..2f11fc544 Binary files /dev/null and b/public/images/game_selection/DeepRockGalacticSurvivor.png differ diff --git a/public/images/game_selection/DiscoElysium.webp b/public/images/game_selection/DiscoElysium.webp new file mode 100644 index 000000000..7f9650a38 Binary files /dev/null and b/public/images/game_selection/DiscoElysium.webp differ diff --git a/public/images/game_selection/Distance.png b/public/images/game_selection/Distance.png new file mode 100644 index 000000000..08a22cc43 Binary files /dev/null and b/public/images/game_selection/Distance.png differ diff --git a/public/images/game_selection/Dredge.png b/public/images/game_selection/Dredge.png new file mode 100644 index 000000000..dc1128416 Binary files /dev/null and b/public/images/game_selection/Dredge.png differ diff --git a/public/images/game_selection/DysonSphereProgram.png b/public/images/game_selection/DysonSphereProgram.png new file mode 100644 index 000000000..c07d2fa3b Binary files /dev/null and b/public/images/game_selection/DysonSphereProgram.png differ diff --git a/public/images/game_selection/ENADreamBBQ.png b/public/images/game_selection/ENADreamBBQ.png new file mode 100644 index 000000000..ad7716b34 Binary files /dev/null and b/public/images/game_selection/ENADreamBBQ.png differ diff --git a/public/images/game_selection/EnterTheGungeon.jpg b/public/images/game_selection/EnterTheGungeon.jpg new file mode 100644 index 000000000..8bcc520a7 Binary files /dev/null and b/public/images/game_selection/EnterTheGungeon.jpg differ diff --git a/public/images/game_selection/Erenshor.jpg b/public/images/game_selection/Erenshor.jpg new file mode 100644 index 000000000..e8f6e74e9 Binary files /dev/null and b/public/images/game_selection/Erenshor.jpg differ diff --git a/public/images/game_selection/FiveNightsAtFreddysIntoThePit.png b/public/images/game_selection/FiveNightsAtFreddysIntoThePit.png new file mode 100644 index 000000000..8321a4fb6 Binary files /dev/null and b/public/images/game_selection/FiveNightsAtFreddysIntoThePit.png differ diff --git a/public/images/game_selection/ForTheKing.png b/public/images/game_selection/ForTheKing.png new file mode 100644 index 000000000..2629666eb Binary files /dev/null and b/public/images/game_selection/ForTheKing.png differ diff --git a/public/images/game_selection/GTFO.jpg b/public/images/game_selection/GTFO.jpg new file mode 100644 index 000000000..7c114000c Binary files /dev/null and b/public/images/game_selection/GTFO.jpg differ diff --git a/public/images/game_selection/GangBeasts.png b/public/images/game_selection/GangBeasts.png new file mode 100644 index 000000000..6a185ffb9 Binary files /dev/null and b/public/images/game_selection/GangBeasts.png differ diff --git a/public/images/game_selection/Gatekeeper.png b/public/images/game_selection/Gatekeeper.png new file mode 100644 index 000000000..7e795b61f Binary files /dev/null and b/public/images/game_selection/Gatekeeper.png differ diff --git a/public/images/game_selection/GladioMori.png b/public/images/game_selection/GladioMori.png new file mode 100644 index 000000000..b138b19e6 Binary files /dev/null and b/public/images/game_selection/GladioMori.png differ diff --git a/public/images/game_selection/Gloomwood.png b/public/images/game_selection/Gloomwood.png new file mode 100644 index 000000000..27c731151 Binary files /dev/null and b/public/images/game_selection/Gloomwood.png differ diff --git a/public/images/game_selection/GoodbyeVolcanoHigh.png b/public/images/game_selection/GoodbyeVolcanoHigh.png new file mode 100644 index 000000000..ad5152d22 Binary files /dev/null and b/public/images/game_selection/GoodbyeVolcanoHigh.png differ diff --git a/public/images/game_selection/GoreBox.png b/public/images/game_selection/GoreBox.png new file mode 100644 index 000000000..bce666e87 Binary files /dev/null and b/public/images/game_selection/GoreBox.png differ diff --git a/public/images/game_selection/GreenHellVR.png b/public/images/game_selection/GreenHellVR.png new file mode 100644 index 000000000..e9e87f3ea Binary files /dev/null and b/public/images/game_selection/GreenHellVR.png differ diff --git a/public/images/game_selection/GuiltyasSock.png b/public/images/game_selection/GuiltyasSock.png new file mode 100644 index 000000000..8eae2db69 Binary files /dev/null and b/public/images/game_selection/GuiltyasSock.png differ diff --git a/public/images/game_selection/H3VR.png b/public/images/game_selection/H3VR.png new file mode 100644 index 000000000..8505f1723 Binary files /dev/null and b/public/images/game_selection/H3VR.png differ diff --git a/public/images/game_selection/HOTDS.jpg b/public/images/game_selection/HOTDS.jpg new file mode 100644 index 000000000..dc4de4158 Binary files /dev/null and b/public/images/game_selection/HOTDS.jpg differ diff --git a/public/images/game_selection/Hades2.png b/public/images/game_selection/Hades2.png new file mode 100644 index 000000000..7fbc80d29 Binary files /dev/null and b/public/images/game_selection/Hades2.png differ diff --git a/public/images/game_selection/HardBullet.jpg b/public/images/game_selection/HardBullet.jpg new file mode 100644 index 000000000..d64493581 Binary files /dev/null and b/public/images/game_selection/HardBullet.jpg differ diff --git a/public/images/game_selection/HardTime3.png b/public/images/game_selection/HardTime3.png new file mode 100644 index 000000000..34cf61d47 Binary files /dev/null and b/public/images/game_selection/HardTime3.png differ diff --git a/public/images/game_selection/HumanFallFlat.png b/public/images/game_selection/HumanFallFlat.png new file mode 100644 index 000000000..26ee6dc55 Binary files /dev/null and b/public/images/game_selection/HumanFallFlat.png differ diff --git a/public/images/game_selection/IAmYourBeast.webp b/public/images/game_selection/IAmYourBeast.webp new file mode 100644 index 000000000..0eae38a2c Binary files /dev/null and b/public/images/game_selection/IAmYourBeast.webp differ diff --git a/public/images/game_selection/Inscryption.png b/public/images/game_selection/Inscryption.png new file mode 100644 index 000000000..4633e9b24 Binary files /dev/null and b/public/images/game_selection/Inscryption.png differ diff --git a/public/images/game_selection/Labyrinthine.png b/public/images/game_selection/Labyrinthine.png new file mode 100644 index 000000000..ec9b885ff Binary files /dev/null and b/public/images/game_selection/Labyrinthine.png differ diff --git a/public/images/game_selection/LastTrainOuttaWormtown.png b/public/images/game_selection/LastTrainOuttaWormtown.png new file mode 100644 index 000000000..aefad9b39 Binary files /dev/null and b/public/images/game_selection/LastTrainOuttaWormtown.png differ diff --git a/public/images/game_selection/LethalCompany.png b/public/images/game_selection/LethalCompany.png new file mode 100644 index 000000000..0a3b9b584 Binary files /dev/null and b/public/images/game_selection/LethalCompany.png differ diff --git a/public/images/game_selection/LethalLeagueBlaze.png b/public/images/game_selection/LethalLeagueBlaze.png new file mode 100644 index 000000000..1ab69d218 Binary files /dev/null and b/public/images/game_selection/LethalLeagueBlaze.png differ diff --git a/public/images/game_selection/LostSkies.png b/public/images/game_selection/LostSkies.png new file mode 100644 index 000000000..4818029d1 Binary files /dev/null and b/public/images/game_selection/LostSkies.png differ diff --git a/public/images/game_selection/Lycans.png b/public/images/game_selection/Lycans.png new file mode 100644 index 000000000..81c34f5d3 Binary files /dev/null and b/public/images/game_selection/Lycans.png differ diff --git a/public/images/game_selection/Magicite.png b/public/images/game_selection/Magicite.png new file mode 100644 index 000000000..507b0f312 Binary files /dev/null and b/public/images/game_selection/Magicite.png differ diff --git a/public/images/game_selection/Magicraft.png b/public/images/game_selection/Magicraft.png new file mode 100644 index 000000000..61f8d4630 Binary files /dev/null and b/public/images/game_selection/Magicraft.png differ diff --git a/public/images/game_selection/Mechanica.jpg b/public/images/game_selection/Mechanica.jpg new file mode 100644 index 000000000..fafd5662e Binary files /dev/null and b/public/images/game_selection/Mechanica.jpg differ diff --git a/public/images/game_selection/MeepleStation.png b/public/images/game_selection/MeepleStation.png new file mode 100644 index 000000000..06596e849 Binary files /dev/null and b/public/images/game_selection/MeepleStation.png differ diff --git a/public/images/game_selection/MiSide.webp b/public/images/game_selection/MiSide.webp new file mode 100644 index 000000000..f17f58334 Binary files /dev/null and b/public/images/game_selection/MiSide.webp differ diff --git a/public/images/game_selection/MonsterTrain2.png b/public/images/game_selection/MonsterTrain2.png new file mode 100644 index 000000000..f6995b5a7 Binary files /dev/null and b/public/images/game_selection/MonsterTrain2.png differ diff --git a/public/images/game_selection/Muck.png b/public/images/game_selection/Muck.png new file mode 100644 index 000000000..3cf08095d Binary files /dev/null and b/public/images/game_selection/Muck.png differ diff --git a/public/images/game_selection/MyDreamSetup.png b/public/images/game_selection/MyDreamSetup.png new file mode 100644 index 000000000..ad3cf9af7 Binary files /dev/null and b/public/images/game_selection/MyDreamSetup.png differ diff --git a/public/images/game_selection/NASB.jpg b/public/images/game_selection/NASB.jpg new file mode 100644 index 000000000..9c798ead0 Binary files /dev/null and b/public/images/game_selection/NASB.jpg differ diff --git a/public/images/game_selection/NearlyDead.png b/public/images/game_selection/NearlyDead.png new file mode 100644 index 000000000..08bac10bd Binary files /dev/null and b/public/images/game_selection/NearlyDead.png differ diff --git a/public/images/game_selection/NineSols.png b/public/images/game_selection/NineSols.png new file mode 100644 index 000000000..b99266b2a Binary files /dev/null and b/public/images/game_selection/NineSols.png differ diff --git a/public/images/game_selection/ObraDinn.png b/public/images/game_selection/ObraDinn.png new file mode 100644 index 000000000..ddb9f6e4b Binary files /dev/null and b/public/images/game_selection/ObraDinn.png differ diff --git a/public/images/game_selection/OddRemedy.png b/public/images/game_selection/OddRemedy.png new file mode 100644 index 000000000..06d60fc54 Binary files /dev/null and b/public/images/game_selection/OddRemedy.png differ diff --git a/public/images/game_selection/OldMarketSimulator.png b/public/images/game_selection/OldMarketSimulator.png new file mode 100644 index 000000000..b2680f91d Binary files /dev/null and b/public/images/game_selection/OldMarketSimulator.png differ diff --git a/public/images/game_selection/Outward.jpg b/public/images/game_selection/Outward.jpg new file mode 100644 index 000000000..ecc720138 Binary files /dev/null and b/public/images/game_selection/Outward.jpg differ diff --git a/public/images/game_selection/OutwardDe.jpg b/public/images/game_selection/OutwardDe.jpg new file mode 100644 index 000000000..baa7b04b1 Binary files /dev/null and b/public/images/game_selection/OutwardDe.jpg differ diff --git a/public/images/game_selection/PEAK.webp b/public/images/game_selection/PEAK.webp new file mode 100644 index 000000000..c578fad12 Binary files /dev/null and b/public/images/game_selection/PEAK.webp differ diff --git a/public/images/game_selection/PIGFACE.png b/public/images/game_selection/PIGFACE.png new file mode 100644 index 000000000..ef639f54e Binary files /dev/null and b/public/images/game_selection/PIGFACE.png differ diff --git a/public/images/game_selection/PaintingVr.png b/public/images/game_selection/PaintingVr.png new file mode 100644 index 000000000..febbdc552 Binary files /dev/null and b/public/images/game_selection/PaintingVr.png differ diff --git a/public/images/game_selection/Palworld.png b/public/images/game_selection/Palworld.png new file mode 100644 index 000000000..dbb8690c7 Binary files /dev/null and b/public/images/game_selection/Palworld.png differ diff --git a/public/images/game_selection/Panicore.png b/public/images/game_selection/Panicore.png new file mode 100644 index 000000000..cb0c9fc24 Binary files /dev/null and b/public/images/game_selection/Panicore.png differ diff --git a/public/images/game_selection/PaqueretteDownTheBunburrows.png b/public/images/game_selection/PaqueretteDownTheBunburrows.png new file mode 100644 index 000000000..6a7b7fe9b Binary files /dev/null and b/public/images/game_selection/PaqueretteDownTheBunburrows.png differ diff --git a/public/images/game_selection/PeaksOfYore.png b/public/images/game_selection/PeaksOfYore.png new file mode 100644 index 000000000..c97ee56f3 Binary files /dev/null and b/public/images/game_selection/PeaksOfYore.png differ diff --git a/public/images/game_selection/Peglin.jpg b/public/images/game_selection/Peglin.jpg new file mode 100644 index 000000000..057568510 Binary files /dev/null and b/public/images/game_selection/Peglin.jpg differ diff --git a/public/images/game_selection/Plasma.jpg b/public/images/game_selection/Plasma.jpg new file mode 100644 index 000000000..3da5b0b7b Binary files /dev/null and b/public/images/game_selection/Plasma.jpg differ diff --git a/public/images/game_selection/PotionCraft.jpg b/public/images/game_selection/PotionCraft.jpg new file mode 100644 index 000000000..ac15332ba Binary files /dev/null and b/public/images/game_selection/PotionCraft.jpg differ diff --git a/public/images/game_selection/PulsarLostColony.png b/public/images/game_selection/PulsarLostColony.png new file mode 100644 index 000000000..80121dc9b Binary files /dev/null and b/public/images/game_selection/PulsarLostColony.png differ diff --git a/public/images/game_selection/REPO.png b/public/images/game_selection/REPO.png new file mode 100644 index 000000000..4fcf52d67 Binary files /dev/null and b/public/images/game_selection/REPO.png differ diff --git a/public/images/game_selection/ROUNDS.png b/public/images/game_selection/ROUNDS.png new file mode 100644 index 000000000..253f862d6 Binary files /dev/null and b/public/images/game_selection/ROUNDS.png differ diff --git a/public/images/game_selection/RUMBLE.png b/public/images/game_selection/RUMBLE.png new file mode 100644 index 000000000..e26fcf42a Binary files /dev/null and b/public/images/game_selection/RUMBLE.png differ diff --git a/public/images/game_selection/Ravenfield.jpg b/public/images/game_selection/Ravenfield.jpg new file mode 100644 index 000000000..d4950b7ad Binary files /dev/null and b/public/images/game_selection/Ravenfield.jpg differ diff --git a/public/images/game_selection/RiskOfRain2.jpg b/public/images/game_selection/RiskOfRain2.jpg new file mode 100644 index 000000000..102677a79 Binary files /dev/null and b/public/images/game_selection/RiskOfRain2.jpg differ diff --git a/public/images/game_selection/RiskOfRainReturns.png b/public/images/game_selection/RiskOfRainReturns.png new file mode 100644 index 000000000..6e6505b6f Binary files /dev/null and b/public/images/game_selection/RiskOfRainReturns.png differ diff --git a/public/images/game_selection/RogueGenesia.jpg b/public/images/game_selection/RogueGenesia.jpg new file mode 100644 index 000000000..bb0da72d9 Binary files /dev/null and b/public/images/game_selection/RogueGenesia.jpg differ diff --git a/public/images/game_selection/RogueTower.jpg b/public/images/game_selection/RogueTower.jpg new file mode 100644 index 000000000..5e3924869 Binary files /dev/null and b/public/images/game_selection/RogueTower.jpg differ diff --git a/public/images/game_selection/STRAFTAT.png b/public/images/game_selection/STRAFTAT.png new file mode 100644 index 000000000..aee2efb8a Binary files /dev/null and b/public/images/game_selection/STRAFTAT.png differ diff --git a/public/images/game_selection/SULFUR.png b/public/images/game_selection/SULFUR.png new file mode 100644 index 000000000..fb97a40ff Binary files /dev/null and b/public/images/game_selection/SULFUR.png differ diff --git a/public/images/game_selection/Sailwind.png b/public/images/game_selection/Sailwind.png new file mode 100644 index 000000000..ce62f3b1a Binary files /dev/null and b/public/images/game_selection/Sailwind.png differ diff --git a/public/images/game_selection/ScheduleI.png b/public/images/game_selection/ScheduleI.png new file mode 100644 index 000000000..215d79e61 Binary files /dev/null and b/public/images/game_selection/ScheduleI.png differ diff --git a/public/images/game_selection/ScrewDrivers.png b/public/images/game_selection/ScrewDrivers.png new file mode 100644 index 000000000..1e82f2009 Binary files /dev/null and b/public/images/game_selection/ScrewDrivers.png differ diff --git a/public/images/game_selection/Shapez2.png b/public/images/game_selection/Shapez2.png new file mode 100644 index 000000000..4d14c3aa8 Binary files /dev/null and b/public/images/game_selection/Shapez2.png differ diff --git a/public/images/game_selection/SlipstreamRogueSpace.png b/public/images/game_selection/SlipstreamRogueSpace.png new file mode 100644 index 000000000..c96ae9af7 Binary files /dev/null and b/public/images/game_selection/SlipstreamRogueSpace.png differ diff --git a/public/images/game_selection/SongsOfConquest.png b/public/images/game_selection/SongsOfConquest.png new file mode 100644 index 000000000..03f5217c6 Binary files /dev/null and b/public/images/game_selection/SongsOfConquest.png differ diff --git a/public/images/game_selection/Stacklands.jpg b/public/images/game_selection/Stacklands.jpg new file mode 100644 index 000000000..770275e4d Binary files /dev/null and b/public/images/game_selection/Stacklands.jpg differ diff --git a/public/images/game_selection/Starsand.png b/public/images/game_selection/Starsand.png new file mode 100644 index 000000000..f06bc0b8b Binary files /dev/null and b/public/images/game_selection/Starsand.png differ diff --git a/public/images/game_selection/Subnautica.png b/public/images/game_selection/Subnautica.png new file mode 100644 index 000000000..5cd7a0e63 Binary files /dev/null and b/public/images/game_selection/Subnautica.png differ diff --git a/public/images/game_selection/SubnauticaBelowZero.png b/public/images/game_selection/SubnauticaBelowZero.png new file mode 100644 index 000000000..8b7825bf5 Binary files /dev/null and b/public/images/game_selection/SubnauticaBelowZero.png differ diff --git a/public/images/game_selection/Subterranauts.png b/public/images/game_selection/Subterranauts.png new file mode 100644 index 000000000..52b5ce422 Binary files /dev/null and b/public/images/game_selection/Subterranauts.png differ diff --git a/public/images/game_selection/Subterror.png b/public/images/game_selection/Subterror.png new file mode 100644 index 000000000..d710addcc Binary files /dev/null and b/public/images/game_selection/Subterror.png differ diff --git a/public/images/game_selection/Sunkenland.jpg b/public/images/game_selection/Sunkenland.jpg new file mode 100644 index 000000000..1d11197f5 Binary files /dev/null and b/public/images/game_selection/Sunkenland.jpg differ diff --git a/public/images/game_selection/SupermarketTogether.png b/public/images/game_selection/SupermarketTogether.png new file mode 100644 index 000000000..a9bdfb581 Binary files /dev/null and b/public/images/game_selection/SupermarketTogether.png differ diff --git a/public/images/game_selection/TCGCardShopSimulator.png b/public/images/game_selection/TCGCardShopSimulator.png new file mode 100644 index 000000000..fcbf2ef81 Binary files /dev/null and b/public/images/game_selection/TCGCardShopSimulator.png differ diff --git a/public/images/game_selection/TaleSpire.jpg b/public/images/game_selection/TaleSpire.jpg new file mode 100644 index 000000000..0260ccb2f Binary files /dev/null and b/public/images/game_selection/TaleSpire.jpg differ diff --git a/public/images/game_selection/TankTeam.png b/public/images/game_selection/TankTeam.png new file mode 100644 index 000000000..3881dcf63 Binary files /dev/null and b/public/images/game_selection/TankTeam.png differ diff --git a/public/images/game_selection/ThunderstoreBeta.jpg b/public/images/game_selection/ThunderstoreBeta.jpg new file mode 100644 index 000000000..fc6cdd13a Binary files /dev/null and b/public/images/game_selection/ThunderstoreBeta.jpg differ diff --git a/public/images/game_selection/Timberborn.png b/public/images/game_selection/Timberborn.png new file mode 100644 index 000000000..93aee0bc0 Binary files /dev/null and b/public/images/game_selection/Timberborn.png differ diff --git a/public/images/game_selection/Titanfall2.jpg b/public/images/game_selection/Titanfall2.jpg new file mode 100644 index 000000000..40fa4ce8f Binary files /dev/null and b/public/images/game_selection/Titanfall2.jpg differ diff --git a/public/images/game_selection/TotallyAccurateBattleSimulator.jpg b/public/images/game_selection/TotallyAccurateBattleSimulator.jpg new file mode 100644 index 000000000..2bf8556ea Binary files /dev/null and b/public/images/game_selection/TotallyAccurateBattleSimulator.jpg differ diff --git a/public/images/game_selection/TouhouLostBranchOfLegend.jpg b/public/images/game_selection/TouhouLostBranchOfLegend.jpg new file mode 100644 index 000000000..1d43453c2 Binary files /dev/null and b/public/images/game_selection/TouhouLostBranchOfLegend.jpg differ diff --git a/public/images/game_selection/TromboneChamp.jpg b/public/images/game_selection/TromboneChamp.jpg new file mode 100644 index 000000000..22f8fb136 Binary files /dev/null and b/public/images/game_selection/TromboneChamp.jpg differ diff --git a/public/images/game_selection/ULTRAKILL.jpg b/public/images/game_selection/ULTRAKILL.jpg new file mode 100644 index 000000000..4397ffe0a Binary files /dev/null and b/public/images/game_selection/ULTRAKILL.jpg differ diff --git a/public/images/game_selection/VRising.jpg b/public/images/game_selection/VRising.jpg new file mode 100644 index 000000000..83ebab9a6 Binary files /dev/null and b/public/images/game_selection/VRising.jpg differ diff --git a/public/images/game_selection/Valheim.jpg b/public/images/game_selection/Valheim.jpg new file mode 100644 index 000000000..4019cd2ff Binary files /dev/null and b/public/images/game_selection/Valheim.jpg differ diff --git a/public/images/game_selection/Vertigo2.png b/public/images/game_selection/Vertigo2.png new file mode 100644 index 000000000..b4f9ef405 Binary files /dev/null and b/public/images/game_selection/Vertigo2.png differ diff --git a/public/images/game_selection/VoidCrew.png b/public/images/game_selection/VoidCrew.png new file mode 100644 index 000000000..65b6d91c6 Binary files /dev/null and b/public/images/game_selection/VoidCrew.png differ diff --git a/public/images/game_selection/VotV.png b/public/images/game_selection/VotV.png new file mode 100644 index 000000000..9da6a11c9 Binary files /dev/null and b/public/images/game_selection/VotV.png differ diff --git a/public/images/game_selection/VtolVR.jpg b/public/images/game_selection/VtolVR.jpg new file mode 100644 index 000000000..03973734b Binary files /dev/null and b/public/images/game_selection/VtolVR.jpg differ diff --git a/public/images/game_selection/WEBFISHING.png b/public/images/game_selection/WEBFISHING.png new file mode 100644 index 000000000..5c947a411 Binary files /dev/null and b/public/images/game_selection/WEBFISHING.png differ diff --git a/public/images/game_selection/WLKRR.jpg b/public/images/game_selection/WLKRR.jpg new file mode 100644 index 000000000..77661862a Binary files /dev/null and b/public/images/game_selection/WLKRR.jpg differ diff --git a/public/images/game_selection/WhiteKnuckle.png b/public/images/game_selection/WhiteKnuckle.png new file mode 100644 index 000000000..8153395e1 Binary files /dev/null and b/public/images/game_selection/WhiteKnuckle.png differ diff --git a/public/images/game_selection/WizardOfLegend.jpg b/public/images/game_selection/WizardOfLegend.jpg new file mode 100644 index 000000000..6ac31aad0 Binary files /dev/null and b/public/images/game_selection/WizardOfLegend.jpg differ diff --git a/public/images/game_selection/WizardWithAGun.jpg b/public/images/game_selection/WizardWithAGun.jpg new file mode 100644 index 000000000..966127e18 Binary files /dev/null and b/public/images/game_selection/WizardWithAGun.jpg differ diff --git a/public/images/game_selection/ancient-dungeon-vr.png b/public/images/game_selection/ancient-dungeon-vr.png new file mode 100644 index 000000000..2ab7261c5 Binary files /dev/null and b/public/images/game_selection/ancient-dungeon-vr.png differ diff --git a/public/images/game_selection/atrio-the-dark-wild.jpg b/public/images/game_selection/atrio-the-dark-wild.jpg new file mode 100644 index 000000000..45d598433 Binary files /dev/null and b/public/images/game_selection/atrio-the-dark-wild.jpg differ diff --git a/public/images/game_selection/bad-north.webp b/public/images/game_selection/bad-north.webp new file mode 100644 index 000000000..50f6b19c7 Binary files /dev/null and b/public/images/game_selection/bad-north.webp differ diff --git a/public/images/game_selection/brotato.jpg b/public/images/game_selection/brotato.jpg new file mode 100644 index 000000000..345650991 Binary files /dev/null and b/public/images/game_selection/brotato.jpg differ diff --git a/public/images/game_selection/dome-keeper.jpg b/public/images/game_selection/dome-keeper.jpg new file mode 100644 index 000000000..5a58060d5 Binary files /dev/null and b/public/images/game_selection/dome-keeper.jpg differ diff --git a/public/images/game_selection/garfield-kart-furious-racing.png b/public/images/game_selection/garfield-kart-furious-racing.png new file mode 100644 index 000000000..3706479ba Binary files /dev/null and b/public/images/game_selection/garfield-kart-furious-racing.png differ diff --git a/public/images/game_selection/lens-island.webp b/public/images/game_selection/lens-island.webp new file mode 100644 index 000000000..7521ddcf7 Binary files /dev/null and b/public/images/game_selection/lens-island.webp differ diff --git a/public/images/game_selection/logic-world.webp b/public/images/game_selection/logic-world.webp new file mode 100644 index 000000000..5abf4d9df Binary files /dev/null and b/public/images/game_selection/logic-world.webp differ diff --git a/public/images/game_selection/lost-skies-island-creator.webp b/public/images/game_selection/lost-skies-island-creator.webp new file mode 100644 index 000000000..2ec951067 Binary files /dev/null and b/public/images/game_selection/lost-skies-island-creator.webp differ diff --git a/public/images/game_selection/mage-arena.webp b/public/images/game_selection/mage-arena.webp new file mode 100644 index 000000000..26081be2c Binary files /dev/null and b/public/images/game_selection/mage-arena.webp differ diff --git a/public/images/game_selection/mycopunk.webp b/public/images/game_selection/mycopunk.webp new file mode 100644 index 000000000..f6b1e19d5 Binary files /dev/null and b/public/images/game_selection/mycopunk.webp differ diff --git a/public/images/game_selection/ostranauts.webp b/public/images/game_selection/ostranauts.webp new file mode 100644 index 000000000..462e502ca Binary files /dev/null and b/public/images/game_selection/ostranauts.webp differ diff --git a/public/images/game_selection/patapon-1-2-replay.webp b/public/images/game_selection/patapon-1-2-replay.webp new file mode 100644 index 000000000..dfaeedaee Binary files /dev/null and b/public/images/game_selection/patapon-1-2-replay.webp differ diff --git a/public/images/game_selection/patch-quest.jpg b/public/images/game_selection/patch-quest.jpg new file mode 100644 index 000000000..c5a89a91c Binary files /dev/null and b/public/images/game_selection/patch-quest.jpg differ diff --git a/public/images/game_selection/receiver-2.jpg b/public/images/game_selection/receiver-2.jpg new file mode 100644 index 000000000..eeab9dac9 Binary files /dev/null and b/public/images/game_selection/receiver-2.jpg differ diff --git a/public/images/game_selection/shadows-of-doubt.jpg b/public/images/game_selection/shadows-of-doubt.jpg new file mode 100644 index 000000000..7408bd3cd Binary files /dev/null and b/public/images/game_selection/shadows-of-doubt.jpg differ diff --git a/public/images/game_selection/shadows-over-loathing.jpg b/public/images/game_selection/shadows-over-loathing.jpg new file mode 100644 index 000000000..02414e0fe Binary files /dev/null and b/public/images/game_selection/shadows-over-loathing.jpg differ diff --git a/public/images/game_selection/skul-the-hero-slayer.jpg b/public/images/game_selection/skul-the-hero-slayer.jpg new file mode 100644 index 000000000..619fb549b Binary files /dev/null and b/public/images/game_selection/skul-the-hero-slayer.jpg differ diff --git a/public/images/game_selection/sons-of-the-forest.jpg b/public/images/game_selection/sons-of-the-forest.jpg new file mode 100644 index 000000000..4d9be7f63 Binary files /dev/null and b/public/images/game_selection/sons-of-the-forest.jpg differ diff --git a/public/images/game_selection/sun-haven.jpg b/public/images/game_selection/sun-haven.jpg new file mode 100644 index 000000000..3b4819f8b Binary files /dev/null and b/public/images/game_selection/sun-haven.jpg differ diff --git a/public/images/game_selection/techtonica.png b/public/images/game_selection/techtonica.png new file mode 100644 index 000000000..880164fad Binary files /dev/null and b/public/images/game_selection/techtonica.png differ diff --git a/public/images/game_selection/the-ouroboros-king.jpg b/public/images/game_selection/the-ouroboros-king.jpg new file mode 100644 index 000000000..c96600dfb Binary files /dev/null and b/public/images/game_selection/the-ouroboros-king.jpg differ diff --git a/public/images/game_selection/the-planet-crafter.jpg b/public/images/game_selection/the-planet-crafter.jpg new file mode 100644 index 000000000..e2017cf65 Binary files /dev/null and b/public/images/game_selection/the-planet-crafter.jpg differ diff --git a/public/images/game_selection/thronefall.png b/public/images/game_selection/thronefall.png new file mode 100644 index 000000000..e33996f30 Binary files /dev/null and b/public/images/game_selection/thronefall.png differ diff --git a/public/images/game_selection/ultimate-chicken-horse.jpg b/public/images/game_selection/ultimate-chicken-horse.jpg new file mode 100644 index 000000000..fd57d299e Binary files /dev/null and b/public/images/game_selection/ultimate-chicken-horse.jpg differ diff --git a/public/images/game_selection/vellum.webp b/public/images/game_selection/vellum.webp new file mode 100644 index 000000000..72c268d1a Binary files /dev/null and b/public/images/game_selection/vellum.webp differ diff --git a/public/images/game_selection/west-of-loathing.jpg b/public/images/game_selection/west-of-loathing.jpg new file mode 100644 index 000000000..a32fdc889 Binary files /dev/null and b/public/images/game_selection/west-of-loathing.jpg differ diff --git a/public/images/game_selection/wildfrost.jpg b/public/images/game_selection/wildfrost.jpg new file mode 100644 index 000000000..ec730a4e5 Binary files /dev/null and b/public/images/game_selection/wildfrost.jpg differ diff --git a/public/images/game_selection/word-play.webp b/public/images/game_selection/word-play.webp new file mode 100644 index 000000000..41e20360c Binary files /dev/null and b/public/images/game_selection/word-play.webp differ diff --git a/public/images/game_selection/wrestling-empire.jpg b/public/images/game_selection/wrestling-empire.jpg new file mode 100644 index 000000000..328c8cf26 Binary files /dev/null and b/public/images/game_selection/wrestling-empire.jpg differ diff --git a/public/images/game_selection/zort.png b/public/images/game_selection/zort.png new file mode 100644 index 000000000..7cde69f41 Binary files /dev/null and b/public/images/game_selection/zort.png differ diff --git a/quasar.conf.js b/quasar.conf.js deleted file mode 100644 index fa5470179..000000000 --- a/quasar.conf.js +++ /dev/null @@ -1,236 +0,0 @@ -/* - * This file runs in a Node context (it's NOT transpiled by Babel), so use only - * the ES6 features that are supported by your Node version. https://node.green/ - */ - -// Configuration for your app -// https://quasar.dev/quasar-cli/quasar-conf-js -/* eslint-disable @typescript-eslint/no-var-requires */ -const { configure } = require('quasar/wrappers'); - -module.exports = configure(function(/* ctx */) { - return { - // https://v1.quasar.dev/quasar-cli/quasar-conf-js#property-sourcefiles - sourceFiles: { - rootComponent: 'src/AppWrapper.vue' - }, - - // https://quasar.dev/quasar-cli/supporting-ts - supportTS: true, - - // https://quasar.dev/quasar-cli/prefetch-feature - // preFetch: true, - - // app boot file (/src/boot) - // --> boot files are part of "main.js" - // https://quasar.dev/quasar-cli/boot-files - boot: [ - 'i18n', - 'axios', - 'floating-vue' // Tooltips - ], - - // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css - css: [ - 'app.scss' - ], - - // https://github.com/quasarframework/quasar/tree/dev/extras - extras: [ - // 'ionicons-v4', - // 'mdi-v5', - // 'fontawesome-v5', - // 'eva-icons', - // 'themify', - // 'line-awesome', - // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! - - 'roboto-font', // optional, you are not bound to it - 'material-icons' // optional, you are not bound to it - ], - - // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build - build: { - vueRouterMode: 'hash', // available values: 'hash', 'history' - - win: { - publish: { - provider: 'github' - } - }, - linux: { - publish: { - provider: 'github' - } - }, - - // transpile: false, - - // Add dependencies for transpiling with Babel (Array of string/regex) - // (from node_modules, which are by default not transpiled). - // Applies only if "transpile" is set to true. - // transpileDependencies: [], - - // rtl: false, // https://quasar.dev/options/rtl-support - // preloadChunks: true, - // showProgress: false, - // gzip: true, - // analyze: true, - - // Options below are automatically set depending on the env, set them if you want to override - // extractCSS: false, - - // https://quasar.dev/quasar-cli/handling-webpack - extendWebpack(cfg) { - } - }, - - // Full list of options: https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer - devServer: { - https: false, - port: 9020, - open: true // opens browser window automatically - }, - - // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework - framework: { - iconSet: 'material-icons', // Quasar icon set - lang: 'en-us', // Quasar language pack - config: {}, - - // Possible values for "importStrategy": - // * 'auto' - (DEFAULT) Auto-import needed Quasar components & directives - // * 'all' - Manually specify what to import - importStrategy: 'auto', - - // For special cases outside of where "auto" importStrategy can have an impact - // (like functional components as one of the examples), - // you can manually specify Quasar components/directives to be available everywhere: - // - // components: [], - // directives: [], - - // Quasar plugins - plugins: [] - }, - - // animations: 'all', // --- includes all animations - // https://quasar.dev/options/animations - animations: [], - - // https://quasar.dev/quasar-cli/developing-ssr/configuring-ssr - ssr: { - pwa: false - }, - - // https://quasar.dev/quasar-cli/developing-pwa/configuring-pwa - pwa: { - workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' - workboxOptions: {}, // only for GenerateSW - manifest: { - name: `r2modmanPlus`, - short_name: `r2modmanPlus`, - description: `A simple and easy to use Risk of Rain 2 mod manager`, - display: 'standalone', - orientation: 'portrait', - background_color: '#ffffff', - theme_color: '#027be3', - icons: [ - { - src: 'icons/icon-128x128.png', - sizes: '128x128', - type: 'image/png' - }, - { - src: 'icons/icon-192x192.png', - sizes: '192x192', - type: 'image/png' - }, - { - src: 'icons/icon-256x256.png', - sizes: '256x256', - type: 'image/png' - }, - { - src: 'icons/icon-384x384.png', - sizes: '384x384', - type: 'image/png' - }, - { - src: 'icons/icon-512x512.png', - sizes: '512x512', - type: 'image/png' - } - ] - } - }, - - // Full list of options: https://quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova - cordova: { - // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing - }, - - // Full list of options: https://quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor - capacitor: { - hideSplashscreen: true - }, - - // Full list of options: https://quasar.dev/quasar-cli/developing-electron-apps/configuring-electron - electron: { - bundler: 'builder', // 'packager' or 'builder' - - packager: { - // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options - - // OS X / Mac App Store - // appBundleId: '', - // appCategoryType: '', - // osxSign: '', - // protocol: 'myapp://path', - - // Windows only - // win32metadata: { ... } - }, - - builder: { - // https://www.electron.build/configuration/configuration - - appId: 'ebkr-r2modman', - win: { - target: ['nsis', 'portable'], - icon: 'src/assets/icon.ico' - }, - nsis: { - oneClick: false, - allowToChangeInstallationDirectory: true, - allowElevation: false, - perMachine: false, - include: 'build/installer.nsh' - }, - linux: { - target: ['AppImage', 'tar.gz', 'deb', 'rpm', 'pacman'], - icon: 'src/assets/icon', - maintainer: 'ebkr', - vendor: 'ebkr', - synopsis: 'Risk of Rain 2 Mod Manager', - category: 'Game', - mimeTypes: [ - "x-scheme-handler/ror2mm" - ] - }, - mac: { - category: "games", - icon: "src/assets/icon" - } - }, - - // More info: https://quasar.dev/quasar-cli/developing-electron-apps/node-integration - nodeIntegration: true, - - extendWebpack(/* cfg */) { - // do something with Electron main process Webpack cfg - // chainWebpack also available besides this extendWebpack - } - } - }; -}); diff --git a/quasar.config.ts b/quasar.config.ts new file mode 100644 index 000000000..34ce00b37 --- /dev/null +++ b/quasar.config.ts @@ -0,0 +1,262 @@ +// Configuration for your app +// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file + +import { defineConfig } from '#q-app/wrappers'; +import { nodePolyfills } from 'vite-plugin-node-polyfills'; + +export default defineConfig((ctx) => { + return { + // https://v2.quasar.dev/quasar-cli-vite/prefetch-feature + // preFetch: true, + + // app boot file (/src/boot) + // --> boot files are part of "main.js" + // https://v2.quasar.dev/quasar-cli-vite/boot-files + boot: [ + 'i18n', + // 'axios', + 'floating-vue' + ], + + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#css + css: [ + 'app.scss' + ], + + // https://github.com/quasarframework/quasar/tree/dev/extras + extras: [ + // 'ionicons-v4', + // 'mdi-v7', + // 'fontawesome-v6', + // 'eva-icons', + // 'themify', + // 'line-awesome', + // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! + + 'roboto-font', // optional, you are not bound to it + 'material-icons', // optional, you are not bound to it + ], + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#build + build: { + target: { + browser: [ 'esnext' ], + node: 'esnext' + }, + + typescript: { + strict: true, + vueShim: true + // extendTsConfig (tsConfig) {} + }, + + vueRouterMode: 'history', // available values: 'hash', 'history' + // vueRouterBase, + // vueDevtools, + // vueOptionsAPI: false, + + rebuildCache: true, // rebuilds Vite/linter/etc cache on startup + + publicPath: '/', + // analyze: true, + // env: {}, + // rawDefine: {} + // ignorePublicFolder: true, + minify: 'esbuild', + polyfillModulePreload: true, + // distDir + + // extendViteConf (viteConf) {}, + viteVuePluginOptions: { + template: { + compilerOptions: { + isCustomElement: (tag) => ["strike"].includes(tag) + } + } + }, + + win: { + publish: { + provider: 'github' + } + }, + linux: { + publish: { + provider: 'github' + } + }, + + vitePlugins: [ + ] + }, + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver + devServer: { + https: false, + port: 9020, + open: true // opens browser window automatically + }, + + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#framework + framework: { + config: {}, + + iconSet: 'material-icons', // Quasar icon set + lang: 'en-US', // Quasar language pack + + // For special cases outside of where the auto-import strategy can have an impact + // (like functional components as one of the examples), + // you can manually specify Quasar components/directives to be available everywhere: + // + // components: [], + // directives: [], + + // Quasar plugins + plugins: [] + }, + + // animations: 'all', // --- includes all animations + // https://v2.quasar.dev/options/animations + animations: [], + + // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles + sourceFiles: { + rootComponent: 'src/AppWrapper.vue', + // router: 'src/router/index', + // store: 'src/store/index', + // pwaRegisterServiceWorker: 'src-pwa/register-service-worker', + // pwaServiceWorker: 'src-pwa/custom-service-worker', + // pwaManifestFile: 'src-pwa/manifest.json', + electronMain: process.env.NODE_ENV === 'development' ? 'src-electron/electron-main.dev' : 'src-electron/electron-main', + electronPreload: 'src-electron/electron-preload' + // bexManifestFile: 'src-bex/manifest.json + }, + + // https://v2.quasar.dev/quasar-cli-vite/developing-ssr/configuring-ssr + ssr: { + prodPort: 3000, // The default port that the production server should use + // (gets superseded if process.env.PORT is specified at runtime) + + middlewares: [ + 'render' // keep this as last one + ], + + // extendPackageJson (json) {}, + // extendSSRWebserverConf (esbuildConf) {}, + + // manualStoreSerialization: true, + // manualStoreSsrContextInjection: true, + // manualStoreHydration: true, + // manualPostHydrationTrigger: true, + + pwa: false + // pwaOfflineHtmlFilename: 'offline.html', // do NOT use index.html as name! + + // pwaExtendGenerateSWOptions (cfg) {}, + // pwaExtendInjectManifestOptions (cfg) {} + }, + + // https://v2.quasar.dev/quasar-cli-vite/developing-pwa/configuring-pwa + pwa: { + workboxMode: 'GenerateSW' // 'GenerateSW' or 'InjectManifest' + // swFilename: 'sw.js', + // manifestFilename: 'manifest.json', + // extendManifestJson (json) {}, + // useCredentialsForManifestTag: true, + // injectPwaMetaTags: false, + // extendPWACustomSWConf (esbuildConf) {}, + // extendGenerateSWOptions (cfg) {}, + // extendInjectManifestOptions (cfg) {} + }, + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-cordova-apps/configuring-cordova + cordova: { + // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing + }, + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-capacitor-apps/configuring-capacitor + capacitor: { + hideSplashscreen: true + }, + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-electron-apps/configuring-electron + electron: { + + // extendElectronMainConf (esbuildConf) {}, + // extendElectronPreloadConf (esbuildConf) {}, + + // extendPackageJson (json) {}, + + // Electron preload scripts (if any) from /src-electron, WITHOUT file extension + preloadScripts: [ 'electron-preload' ], + + // specify the debugging port to use for the Electron app when running in development mode + inspectPort: 5858, + + bundler: 'builder', // 'packager' or 'builder' + + packager: { + // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options + + // OS X / Mac App Store + // appBundleId: '', + // appCategoryType: '', + // osxSign: '', + // protocol: 'myapp://path', + + // Windows only + // win32metadata: { ... } + }, + + builder: { + // https://www.electron.build/configuration/configuration + + appId: 'ebkr-r2modman', + win: { + target: ['nsis', 'portable'], + icon: 'src/assets/icon.ico' + }, + nsis: { + oneClick: false, + allowToChangeInstallationDirectory: true, + allowElevation: false, + perMachine: false, + include: 'build/installer.nsh' + }, + linux: { + target: ['AppImage', 'tar.gz', 'deb', 'rpm', 'pacman'], + icon: 'src/assets/icon', + maintainer: 'ebkr', + vendor: 'ebkr', + synopsis: 'Risk of Rain 2 Mod Manager', + category: 'Game', + mimeTypes: [ + "x-scheme-handler/ror2mm" + ] + }, + mac: { + category: "games", + icon: "src/assets/icon" + } + }, + + nodeIntegration: true, + }, + + // Full list of options: https://v2.quasar.dev/quasar-cli-vite/developing-browser-extensions/configuring-bex + bex: { + // extendBexScriptsConf (esbuildConf) {}, + // extendBexManifestJson (json) {}, + + /** + * The list of extra scripts (js/ts) not in your bex manifest that you want to + * compile and use in your browser extension. Maybe dynamic use them? + * + * Each entry in the list should be a relative filename to /src-bex/ + * + * @example [ 'my-script.ts', 'sub-folder/my-other-script.js' ] + */ + extraScripts: [] + } + } +}); diff --git a/quasar.extensions.json b/quasar.extensions.json deleted file mode 100644 index c2084877e..000000000 --- a/quasar.extensions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "@quasar/typescript": { - "webpack": "plugin", - "rename": true, - "vscode": true, - "prettier": true - }, - "@quasar/testing-unit-jest": { - "babel": "babelrc", - "options": [ - "scripts", - "typescript", - "SFC", - "wallabyjs", - "majestic" - ] - } -} \ No newline at end of file diff --git a/quasar.testing.json b/quasar.testing.json deleted file mode 100644 index a9da8280f..000000000 --- a/quasar.testing.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "unit-jest": { - "runnerCommand": "jest --ci" - } -} diff --git a/src-electron/electron-env.d.ts b/src-electron/electron-env.d.ts new file mode 100644 index 000000000..b05aa81e9 --- /dev/null +++ b/src-electron/electron-env.d.ts @@ -0,0 +1,8 @@ +declare namespace NodeJS { + interface ProcessEnv { + QUASAR_PUBLIC_FOLDER: string; + QUASAR_ELECTRON_PRELOAD_FOLDER: string; + QUASAR_ELECTRON_PRELOAD_EXTENSION: string; + APP_URL: string; + } +} diff --git a/src-electron/electron-flag.d.ts b/src-electron/electron-flag.d.ts deleted file mode 100644 index a78cbf501..000000000 --- a/src-electron/electron-flag.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -// THIS FEATURE-FLAG FILE IS AUTOGENERATED, -// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING -import "quasar/dist/types/feature-flag"; - -declare module "quasar/dist/types/feature-flag" { - interface QuasarFeatureFlags { - electron: true; - } -} diff --git a/src-electron/electron-main.dev.ts b/src-electron/electron-main.dev.ts new file mode 100644 index 000000000..e5c6c6a79 --- /dev/null +++ b/src-electron/electron-main.dev.ts @@ -0,0 +1,7 @@ +/** + * This file is used specifically and only for development. It installs + * `electron-debug` & `vue-devtools`. There shouldn't be any need to + * modify this file, but it can be used to extend your development + * environment. + */ +import './electron-main' diff --git a/src-electron/main-process/electron-main.js b/src-electron/electron-main.ts similarity index 60% rename from src-electron/main-process/electron-main.js rename to src-electron/electron-main.ts index cb1673503..4ad4fc14d 100644 --- a/src-electron/main-process/electron-main.js +++ b/src-electron/electron-main.ts @@ -1,9 +1,12 @@ import { app, BrowserWindow, ipcMain, nativeTheme, protocol } from 'electron'; -import Listeners from './ipcListeners'; -import Persist from './window-state-persist'; +import { Listeners } from './ipcListeners'; +import { Persist } from './window-state-persist'; import path from 'path'; import ipcServer from 'node-ipc'; -import * as fs from 'fs'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import 'app/src-electron/ipc/init-ipc'; +import { hookIpc } from 'app/src-electron/ipc/init-ipc'; app.allowRendererProcessReuse = true; @@ -18,6 +21,10 @@ try { * Set `__statics` path to static files in production; * The reason we are setting it here is that the path needs to be evaluated at runtime */ + +const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file +const __dirname = path.dirname(__filename); + if (process.env.PROD) { global.__statics = __dirname; global.__statics = path.join(__dirname, 'statics').replace(/\\/g, '\\\\'); @@ -26,10 +33,7 @@ if (process.env.PROD) { let mainWindow; -function createWindow() { - /** - * Initial window options - */ +async function createWindow() { const windowSize = Persist.getSize(app, { defaultWidth: 1200, @@ -40,16 +44,21 @@ function createWindow() { width: windowSize.width, height: windowSize.height, useContentSize: true, + icon: path.resolve(__dirname, 'icons/icon.png'), + autoHideMenuBar: process.env.PROD, webPreferences: { - nodeIntegration: true, - nodeIntegrationInWorker: true, - webSecurity: false, - contextIsolation: false - }, - icon: path.join(__dirname, 'icon.png'), - autoHideMenuBar: process.env.PROD + preload: path.resolve( + fileURLToPath(new URL('.', import.meta.url)), + path.join(process.env.QUASAR_ELECTRON_PRELOAD_FOLDER, 'electron-preload' + process.env.QUASAR_ELECTRON_PRELOAD_EXTENSION) + ), + // TODO - Remove and find an appropriate workaround for CORS blocking by Electron + // Likely workaround is to move network requests to backend + webSecurity: false + } }); + hookIpc(mainWindow); + if (windowSize.maximized) { mainWindow.maximize(); } @@ -59,15 +68,19 @@ function createWindow() { // Initialise client to server communication listener new Listeners(mainWindow, app); - mainWindow.loadURL(process.env.APP_URL); + if (process.env.DEV) { + await mainWindow.loadURL(process.env.APP_URL); + } else { + await mainWindow.loadFile('index.html'); + } mainWindow.on('closed', () => { mainWindow = null; }); } -app.on('ready', () => { - createWindow(); +app.on('ready', async () => { + await createWindow(); let reqLockSuccess = app.requestSingleInstanceLock(); if (!reqLockSuccess) { // If this isn't the single instance, @@ -95,15 +108,33 @@ app.on('ready', () => { } }); +protocol.registerSchemesAsPrivileged([ + { + scheme: 'public', + privileges: { + standard: true, + secure: true, + supportFetchAPI: true + } + } +]) + app.whenReady().then(() => { protocol.registerFileProtocol('file', (request, callback) => { const pathname = request.url.replace('file:///', ''); if (fs.existsSync(pathname)) { callback(pathname); } else { - callback(path.join(__statics, "unknown.png")); + callback(path.join(global.__statics, 'unknown.png')); } }); + + protocol.handle('public', async (req: any) => { + const publicFolder = path.resolve(__dirname, process.env.QUASAR_PUBLIC_FOLDER); + const filePath = (req.url as string).substring("public://".length).replace(/\\/g, path.sep); + const fileContent = await fs.promises.readFile(path.join(publicFolder, filePath)) + return new Response(fileContent); + }) }); app.on('window-all-closed', () => { diff --git a/src-electron/electron-preload.ts b/src-electron/electron-preload.ts new file mode 100644 index 000000000..b40751b82 --- /dev/null +++ b/src-electron/electron-preload.ts @@ -0,0 +1,31 @@ +/** + * This file is used specifically for security reasons. + * Here you can access Nodejs stuff and inject functionality into + * the renderer thread (accessible there through the "window" object) + * + * WARNING! + * If you import anything from node_modules, then make sure that the package is specified + * in package.json > dependencies and NOT in devDependencies + * + * Example (injects window.myAPI.doAThing() into renderer thread): + * + * import { contextBridge } from 'electron' + * + * contextBridge.exposeInMainWorld('myAPI', { + * doAThing: () => {} + * }) + * + * WARNING! + * If accessing Node functionality (like importing @electron/remote) then in your + * electron-main.ts you will need to set the following when you instantiate BrowserWindow: + * + * mainWindow = new BrowserWindow({ + * // ... + * webPreferences: { + * // ... + * sandbox: false // <-- to be able to import @electron/remote in preload script + * } + * } + */ + +import "./preload/expose-preload"; diff --git a/src-electron/icons/icon.icns b/src-electron/icons/icon.icns index 9cc7e8e5a..8a28a4beb 100644 Binary files a/src-electron/icons/icon.icns and b/src-electron/icons/icon.icns differ diff --git a/src-electron/icons/icon.ico b/src-electron/icons/icon.ico index ae49a0194..6944eda46 100644 Binary files a/src-electron/icons/icon.ico and b/src-electron/icons/icon.ico differ diff --git a/src-electron/icons/icon.png b/src-electron/icons/icon.png new file mode 100644 index 000000000..4347f17cf Binary files /dev/null and b/src-electron/icons/icon.png differ diff --git a/src-electron/ipc/electron-hook.ts b/src-electron/ipc/electron-hook.ts new file mode 100644 index 000000000..268722cf3 --- /dev/null +++ b/src-electron/ipc/electron-hook.ts @@ -0,0 +1,20 @@ +import { BrowserWindow, ipcMain, shell, clipboard } from 'electron'; + +export function hookElectronIpc(browserWindow: BrowserWindow) { + ipcMain.on('electron:shell:openExternal', (event, url) => { + shell.openExternal(url) + }); + + ipcMain.on('electron:shell:selectFile', (event, filePath) => { + shell.showItemInFolder(filePath) + }); + + ipcMain.on('electron:shell:openPath', (event, filePath) => { + shell.openPath(filePath) + }); + + ipcMain.on('electron:clipboard:copyText', (event, text) => { + clipboard.writeText(text); + event.returnValue = true; + }); +} diff --git a/src-electron/ipc/init-ipc.ts b/src-electron/ipc/init-ipc.ts new file mode 100644 index 000000000..67e802018 --- /dev/null +++ b/src-electron/ipc/init-ipc.ts @@ -0,0 +1,16 @@ +import { BrowserWindow } from 'electron'; +import { hookPathIpc } from './node-path-impl'; +import { hookChildProcessIpc } from './node-child-process-impl'; +import { hookFsIpc } from 'app/src-electron/ipc/node-fs-impl'; +import { hookZipIpc } from 'app/src-electron/ipc/zip-hook'; +import { hookElectronIpc } from 'app/src-electron/ipc/electron-hook'; +import {hookOsIpc} from "app/src-electron/ipc/node-os-impl"; + +export function hookIpc(browserWindow: BrowserWindow) { + hookPathIpc(browserWindow); + hookChildProcessIpc(browserWindow); + hookFsIpc(browserWindow); + hookOsIpc(browserWindow); + hookZipIpc(browserWindow); + hookElectronIpc(browserWindow); +} diff --git a/src-electron/ipc/node-child-process-impl.ts b/src-electron/ipc/node-child-process-impl.ts new file mode 100644 index 000000000..b9f253a41 --- /dev/null +++ b/src-electron/ipc/node-child-process-impl.ts @@ -0,0 +1,16 @@ +import { BrowserWindow, ipcMain } from 'electron'; +import ChildProcess from 'child_process'; + +export function hookChildProcessIpc(browserWindow: BrowserWindow) { + ipcMain.on("node:child_process:execSync", (event, path) => { + event.returnValue = ChildProcess.execSync(path).toString(); + }) + + ipcMain.handle("node:child_process:exec", (event, path, options) => { + return new Promise(resolve => { + ChildProcess.exec(path, options, err => { + resolve(err); + }); + }) + }) +} diff --git a/src-electron/ipc/node-fs-impl.ts b/src-electron/ipc/node-fs-impl.ts new file mode 100644 index 000000000..c2ada96a4 --- /dev/null +++ b/src-electron/ipc/node-fs-impl.ts @@ -0,0 +1,122 @@ +import { BrowserWindow, ipcMain } from 'electron'; +import fs from 'fs'; +import path from 'path'; + +export function hookFsIpc(browserWindow: BrowserWindow) { + ipcMain.handle('node:fs:writeFile', (event, path, content) => { + return fs.promises.writeFile(path, content); + }); + + ipcMain.handle('node:fs:readFile', (event, path) => { + return fs.promises.readFile(path, { + encoding: 'utf8' + }); + }); + + ipcMain.handle('node:fs:exists', async (event, path) => { + return exists(path); + }); + + ipcMain.handle('node:fs:mkdirs', async (event, path) => { + return mkdirs(path); + }); + + ipcMain.handle('node:fs:readdir', (event, path) => { + return fs.promises.readdir(path); + }); + + ipcMain.handle('node:fs:stat', (event, path) => { + return fs.promises.stat(path).then(result => generateSerializableStat(result)); + }); + + ipcMain.handle('node:fs:lstat', (event, path) => { + return fs.promises.lstat(path).then(result => generateSerializableStat(result)); + }); + + ipcMain.handle('node:fs:rmdir', (event, path) => { + return fs.promises.rmdir(path); + }); + + ipcMain.handle('node:fs:unlink', (event, path) => { + return fs.promises.unlink(path); + }); + + ipcMain.handle('node:fs:realpath', (event, path) => { + return fs.promises.realpath(path); + }); + + ipcMain.handle('node:fs:rename', (event, path, newPath) => { + return fs.promises.rename(path, newPath); + }); + + ipcMain.handle('node:fs:chmod', (event, path, mode) => { + return fs.promises.chmod(path, mode); + }); + + ipcMain.handle('node:fs:copyFile', (event, from, to) => { + return copyFile(from, to); + }); + + ipcMain.handle('node:fs:copyFolder', async (event, from, to) => { + return copyFolder(from, to); + }); + + ipcMain.handle('node:fs:base64FromZip', (event, path) => { + return fs.promises.readFile(path, { + encoding: 'base64' + }); + }); + + ipcMain.handle('node:fs:setModifiedTime', (event, path, time) => { + return fs.promises.utimes(path, time, time) + }); +} + +async function copyFile(from: string, to: string) { + return fs.promises.copyFile(from, to) +} + +async function copyFolder(from: string, to: string) { + return fs.promises.readdir(from) + .then(async result => { + for (const item of result) { + const fromLstat = await fs.promises.lstat(path.join(from, item)); + if (fromLstat.isDirectory()) { + const toDirectoryExists = await exists(path.join(to, item)); + if (!toDirectoryExists) { + await mkdirs(path.join(to, item)); + } + await copyFolder(path.join(from, item), path.join(to, item)); + } else { + await mkdirs(path.dirname(path.join(to, item))) + await copyFile(path.join(from, item), path.join(to, item)); + } + } + }); +} + +async function mkdirs(mkdirPath: string) { + return fs.promises.mkdir(mkdirPath, { recursive: true }); +} + +async function exists(path: string) { + return fs.promises.access(path, fs.constants.F_OK) + .then(() => true) + .catch(() => false); +} + +type SerializableStat = Omit & { + isDirectory: boolean; + isFile: boolean; +}; + +function generateSerializableStat(statLike: fs.Stats): SerializableStat { + const unpackedStatLike = { + ...statLike + } as fs.StatsBase; + return { + ...unpackedStatLike, + isDirectory: statLike.isDirectory(), + isFile: statLike.isFile(), + }; +} diff --git a/src-electron/ipc/node-os-impl.ts b/src-electron/ipc/node-os-impl.ts new file mode 100644 index 000000000..394496761 --- /dev/null +++ b/src-electron/ipc/node-os-impl.ts @@ -0,0 +1,8 @@ +import {BrowserWindow, ipcMain} from "electron"; +import os from "os"; + +export function hookOsIpc(browserWindow: BrowserWindow) { + ipcMain.on('node:os:homedir', (event) => { + event.returnValue = os.homedir(); + }) +} diff --git a/src-electron/ipc/node-path-impl.ts b/src-electron/ipc/node-path-impl.ts new file mode 100644 index 000000000..850f6e281 --- /dev/null +++ b/src-electron/ipc/node-path-impl.ts @@ -0,0 +1,23 @@ +import { BrowserWindow, ipcMain } from 'electron'; +import path from 'path'; + +export function hookPathIpc(browserWindow: BrowserWindow) { + ipcMain.on("node:path:join", (event, ...args) => { + event.returnValue = path.join(...args); + }); + ipcMain.on("node:path:basename", (event, toResolve) => { + event.returnValue = path.basename(toResolve); + }); + ipcMain.on("node:path:resolve", (event, ...args) => { + event.returnValue = path.resolve(...args); + }); + ipcMain.on("node:path:extname", (event, pathAsString: string) => { + event.returnValue = path.extname(pathAsString); + }); + ipcMain.on("node:path:relative", (event, pathOne: string, pathTwo: string) => { + event.returnValue = path.relative(pathOne, pathTwo); + }); + ipcMain.on("node:path:dirname", (event, pathAsString: string) => { + event.returnValue = path.dirname(pathAsString); + }); +} diff --git a/src-electron/ipc/zip-hook.ts b/src-electron/ipc/zip-hook.ts new file mode 100644 index 000000000..95019c760 --- /dev/null +++ b/src-electron/ipc/zip-hook.ts @@ -0,0 +1,104 @@ +import { BrowserWindow, ipcMain } from 'electron'; +import AdmZip from 'adm-zip'; +import path from 'path'; + +let zipCreatorIdentifier = 0; +const zipCreatorCache = new Map(); + +export function hookZipIpc(browserWindow: BrowserWindow) { + ipcMain.handle('zip:extractAllTo', (event, zip: string, outputFolder: string) => { + return new Promise((resolve, reject) => { + const adm = new AdmZip(zip); + outputFolder = outputFolder.replace(/\\/g, '/'); + adm.extractAllToAsync(outputFolder, true, error => { + if (error) { + reject(error); + } else { + resolve(error); + } + }); + }); + }); + + ipcMain.handle('zip:readFile', (event, zip: string, fileName: string) => { + return new Promise((resolve, reject) => { + const adm = new AdmZip(zip); + return adm.readFileAsync(fileName, (data, err) => { + if (err) { + reject(err); + } else { + resolve(data?.toString('utf8')); + } + }); + }); + }); + + ipcMain.handle('zip:getEntries', (event, zip: string) => { + return new Promise((resolve, reject) => { + const adm = new AdmZip(zip); + try { + const entries = adm.getEntries(); + resolve(JSON.stringify(entries)); + } catch (e) { + reject(e); + } + }); + }); + + ipcMain.handle('zip:extractEntryTo', (event, zip: string, target: string, outputPath: string) => { + return new Promise((resolve, reject) => { + try { + const adm = new AdmZip(zip); + const safeTarget = target.replace(/\\/g, '/'); + outputPath = outputPath.replace(/\\/g, '/'); + var fullPath = path.join(outputPath, safeTarget).replace(/\\/g, '/'); + if(!path.posix.normalize(fullPath).startsWith(outputPath)) + { + throw new Error("Entry " + target + " would extract outside of expected folder"); + } + adm.extractEntryTo(target, outputPath, true, true); + return resolve(undefined); + } catch (e) { + reject(e); + } + }); + }); + + ipcMain.on('zip:create:new', (event) => { + const identifier = zipCreatorIdentifier++; + zipCreatorCache.set(identifier, new AdmZip()); + event.returnValue = identifier; + }); + + ipcMain.handle(`zip:create:addBuffer`, async (event, identifier, fileName: string, data: Buffer) => { + const zip = zipCreatorCache.get(identifier); + if (zip === undefined) { + throw new Error(`No zip was present in temporary creator with identifier: ${identifier}`); + } + zip.addFile(fileName, data); + }); + + ipcMain.handle(`zip:create:addFolder`, async (event, identifier, zippedFolderName: string, folderNameOnDisk: string) => { + const zip = zipCreatorCache.get(identifier); + if (zip === undefined) { + throw new Error(`No zip was present in temporary creator with identifier: ${identifier}`); + } + zip.addLocalFolder(folderNameOnDisk, zippedFolderName); + }); + + ipcMain.handle(`zip:create:finalize`, (event, identifier, outputPath: string) => { + return new Promise((resolve, reject) => { + const zip = zipCreatorCache.get(identifier); + if (zip === undefined) { + throw new Error(`No zip was present in temporary creator with identifier: ${identifier}`); + } + zip.writeZip(outputPath, err => { + if (err) { + reject(err); + } else { + resolve(undefined); + } + }); + }); + }); +} diff --git a/src-electron/ipcListeners.ts b/src-electron/ipcListeners.ts new file mode 100644 index 000000000..6f0d4bfb3 --- /dev/null +++ b/src-electron/ipcListeners.ts @@ -0,0 +1,93 @@ +import { ipcMain, dialog, App, BrowserWindow, Menu, MenuItem } from 'electron'; +import electronUpdater from 'electron-updater'; +import os from 'os'; +import { fileURLToPath } from 'url'; +import path from 'path'; + +let browserWindow: BrowserWindow; +let app: App; + +export class Listeners { + constructor(window: BrowserWindow, electronApp: App) { + browserWindow = window; + app = electronApp; + } +} + +ipcMain.on('get-browser-window', () => { + browserWindow.webContents.send('receive-browser-window', browserWindow); +}); + +ipcMain.handle('update-app', async () => { + if (typeof process.env.APPIMAGE !== 'undefined' || !process.execPath.startsWith(os.tmpdir())) { + electronUpdater.autoUpdater.autoDownload = true; + await electronUpdater.autoUpdater.checkForUpdatesAndNotify(); + } +}); + +ipcMain.on('install-via-thunderstore', (installString) => { + browserWindow.webContents.send('install-from-thunderstore-string', installString); +}); + +ipcMain.handle('get-appData-directory', () => { + return app.getPath('appData'); +}); + +ipcMain.handle('get-is-portable', async () => { + let isPortable = false; + switch (process.platform) { + case 'win32': + isPortable = process.execPath.startsWith(os.tmpdir()); + break; + case 'linux': + // The correct way to handle this should be + // isPortable = typeof process.env.APPIMAGE !== "undefined"; + // but since Manager.vue needs a refactor, we do the opposite + isPortable = typeof process.env.APPIMAGE === 'undefined'; + break; + } + return isPortable; +}); + +ipcMain.on('restart', () => { + app.relaunch(); + app.exit(); +}); + +ipcMain.on('get-assets-path', () => { + if (process.env.PROD) { + browserWindow.webContents.send('receive-assets-path', global.__statics); + } else { + browserWindow.webContents.send('receive-assets-path', 'src/statics/'); + } +}); + +ipcMain.handle('show-open-dialog', (event, fileOpts) => { + return dialog.showOpenDialog(browserWindow, fileOpts); +}); + +ipcMain.on('get-process-platform', (event) => { + event.returnValue = process.platform; +}); + +ipcMain.on('get-statics-directory', (event) => { + const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file + const __dirname = path.dirname(__filename); + event.returnValue = __dirname; +}); + +ipcMain.on('electron:showContextMenu', (event, options: any) => { + const templateItems: MenuItem[] = []; + templateItems.push(new MenuItem({ + label: 'Copy', + role: 'copy' + })); + if (!options.readonly) { + templateItems.push(new MenuItem({ + label: 'Paste', + role: 'paste' + })); + } + const menu = Menu.buildFromTemplate(templateItems); + menu.popup(); +}) diff --git a/src-electron/main-process/electron-main.dev.js b/src-electron/main-process/electron-main.dev.js deleted file mode 100644 index dda445d90..000000000 --- a/src-electron/main-process/electron-main.dev.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * This file is used specifically and only for development. It installs - * `electron-debug` & `vue-devtools`. There shouldn't be any need to - * modify this file, but it can be used to extend your development - * environment. - */ - -import electronDebug from 'electron-debug' -import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer' -import { app, BrowserWindow } from 'electron' - -app.whenReady().then(() => { - // allow for a small delay for mainWindow to be created - setTimeout(() => { - // Install `electron-debug` with `devtron` - electronDebug({ showDevTools: false }) - - // Install vuejs devtools - installExtension(VUEJS_DEVTOOLS) - .then(name => { - console.log(`Added Extension: ${name}`) - // get main window - const win = BrowserWindow.getFocusedWindow() - if (win) { - win.webContents.on('did-frame-finish-load', () => { - win.webContents.once('devtools-opened', () => { - win.webContents.focus() - }) - // open electron debug - console.log('Opening dev tools') - win.webContents.openDevTools() - }) - } - }) - .catch(err => { - console.log('An error occurred: ', err) - }) - }, 250) -}) - -import './electron-main' diff --git a/src-electron/main-process/ipcListeners.js b/src-electron/main-process/ipcListeners.js deleted file mode 100644 index 6f64b2793..000000000 --- a/src-electron/main-process/ipcListeners.js +++ /dev/null @@ -1,71 +0,0 @@ -import { ipcMain, dialog } from 'electron'; -import { autoUpdater } from 'electron-updater'; -import os from 'os'; - -let browserWindow; -let app; - -export default class Listeners { - constructor(window, electronApp) { - browserWindow = window; - app = electronApp; - } -} - -ipcMain.on('get-browser-window', ()=>{ - browserWindow.webContents.send('receive-browser-window', browserWindow); -}); - -ipcMain.on('update-app', ()=>{ - if (typeof process.env.APPIMAGE !== "undefined" || !process.execPath.startsWith(os.tmpdir())) { - autoUpdater.autoDownload = true; - autoUpdater.checkForUpdatesAndNotify(); - browserWindow.webContents.send('update-done'); - } else { - browserWindow.webContents.send('update-done'); - } -}); - -ipcMain.on('install-via-thunderstore', (installString) => { - browserWindow.webContents.send('install-from-thunderstore-string', installString); -}); - -ipcMain.on('get-appData-directory', ()=>{ - browserWindow.webContents.send('receive-appData-directory', app.getPath('appData')); -}); - -ipcMain.on('get-is-portable', ()=>{ - let isPortable = false; - switch(process.platform){ - case "win32": - isPortable = process.execPath.startsWith(os.tmpdir()); - break; - case "linux": - // The correct way to handle this should be - // isPortable = typeof process.env.APPIMAGE !== "undefined"; - // but since Manager.vue needs a refactor, we do the opposite - isPortable = typeof process.env.APPIMAGE === "undefined"; - break; - } - browserWindow.webContents.send('receive-is-portable', isPortable); -}); - -ipcMain.on('restart', ()=>{ - app.relaunch(); - app.exit(); -}); - -ipcMain.on('get-assets-path', ()=>{ - if (process.env.PROD) { - browserWindow.webContents.send('receive-assets-path', global.__statics); - } else { - browserWindow.webContents.send('receive-assets-path', 'src/statics/'); - } -}); - -ipcMain.on('show-open-dialog', (arg, fileOpts) => { - dialog.showOpenDialog(browserWindow, fileOpts).then(r => { - browserWindow.webContents.send('receive-open-dialog', r); - }); -}); - diff --git a/src-electron/preload/app-preload-globals.ts b/src-electron/preload/app-preload-globals.ts new file mode 100644 index 000000000..b64e870aa --- /dev/null +++ b/src-electron/preload/app-preload-globals.ts @@ -0,0 +1,32 @@ +import {ipcRenderer} from "electron/renderer"; + +export async function getAppDataDirectory(): Promise { + return ipcRenderer.invoke('get-appData-directory'); +} + +export async function isApplicationPortable(): Promise { + return ipcRenderer.invoke('get-is-portable'); +} + +export function getPlatform(): string { + return ipcRenderer.sendSync('get-process-platform'); +} + +export async function checkForApplicationUpdates(): Promise { + return ipcRenderer.invoke('update-app'); +} + +export function getStaticsDirectory(): string { + return ipcRenderer.sendSync('get-statics-directory'); +} + +export function restart() { + ipcRenderer.send('restart'); +} + +export function hookModInstallProtocol(callback: (data: any) => void) { + ipcRenderer.removeAllListeners('install-from-thunderstore-string'); + ipcRenderer.on('install-from-thunderstore-string', (_sender: any, data: string) => { + callback(data); + }); +} diff --git a/src-electron/preload/electron-ipc-preload.ts b/src-electron/preload/electron-ipc-preload.ts new file mode 100644 index 000000000..ce835519f --- /dev/null +++ b/src-electron/preload/electron-ipc-preload.ts @@ -0,0 +1,36 @@ +import { ipcRenderer, OpenDialogOptions } from 'electron'; + +export function openExternal(url: string) { + ipcRenderer.send('electron:shell:openExternal', url); +} + +export function selectFile(url: string) { + ipcRenderer.send('electron:shell:selectFile', url); +} + +export function openPath(url: string) { + ipcRenderer.send('electron:shell:openPath', url); +} +export async function selectFolderDialog(options: any) { + const fileOpts = options as unknown as OpenDialogOptions; + fileOpts.properties = ['openDirectory', 'showHiddenFiles']; + + return ipcRenderer.invoke('show-open-dialog', options) + .then(result => result.filePaths); +} + +export async function selectFileDialog(options: any) { + const fileOpts = options as unknown as OpenDialogOptions; + fileOpts.properties = ['openFile', 'showHiddenFiles']; + + return ipcRenderer.invoke('show-open-dialog', options) + .then(result => result.filePaths); +} + +export function copyToClipboard(value: string) { + ipcRenderer.sendSync('electron:clipboard:copyText', value); +} + +export function showContextMenu(options: any) { + ipcRenderer.send('electron:showContextMenu', options); +} diff --git a/src-electron/preload/expose-preload.ts b/src-electron/preload/expose-preload.ts new file mode 100644 index 000000000..4fc97c54d --- /dev/null +++ b/src-electron/preload/expose-preload.ts @@ -0,0 +1,21 @@ +import { contextBridge } from 'electron'; +import * as path from './node-path'; +import * as child_process from './node-child-process'; +import * as fs from './node-fs'; +import * as buffer from './node-buffer'; +import * as os from './node-os'; +import * as zip from './zip-preload'; +import * as appGlobals from "./app-preload-globals"; +import * as electron from "./electron-ipc-preload"; + +contextBridge.exposeInMainWorld('node', { + path: path, + child_process: child_process, + fs: fs, + buffer: buffer, + os: os, +}); + +contextBridge.exposeInMainWorld('app', appGlobals); +contextBridge.exposeInMainWorld('zip', zip); +contextBridge.exposeInMainWorld('electron', electron); diff --git a/src-electron/preload/node-buffer.ts b/src-electron/preload/node-buffer.ts new file mode 100644 index 000000000..5b9f2be6b --- /dev/null +++ b/src-electron/preload/node-buffer.ts @@ -0,0 +1,3 @@ +export function from(data: any, encoding?: 'utf8' | 'base64') { + return Buffer.from(data, encoding); +} diff --git a/src-electron/preload/node-child-process.ts b/src-electron/preload/node-child-process.ts new file mode 100644 index 000000000..17769a0bc --- /dev/null +++ b/src-electron/preload/node-child-process.ts @@ -0,0 +1,9 @@ +import { ipcRenderer } from 'electron/renderer'; + +export function execSync(identifier: string, path: string, options: any) { + return ipcRenderer.sendSync('node:child_process:execSync', identifier, path, options); +} + +export function exec(path: string, options: any) { + return ipcRenderer.invoke('node:child_process:exec', path, options); +} diff --git a/src-electron/preload/node-fs.ts b/src-electron/preload/node-fs.ts new file mode 100644 index 000000000..168b13503 --- /dev/null +++ b/src-electron/preload/node-fs.ts @@ -0,0 +1,65 @@ +import { ipcRenderer } from 'electron/renderer'; + +export async function writeFile(path: string, content: string | Buffer): Promise { + return ipcRenderer.invoke('node:fs:writeFile', path, content); +} + +export async function readFile(path: string) { + return ipcRenderer.invoke('node:fs:readFile', path); +} + +export async function readdir(path: string): Promise { + return ipcRenderer.invoke('node:fs:readdir', path); +} + +export async function rmdir(path: string) { + return ipcRenderer.invoke('node:fs:rmdir', path); +} + +export async function mkdirs(path: string) { + return ipcRenderer.invoke('node:fs:mkdirs', path); +} + +export async function exists(path: string) { + return ipcRenderer.invoke('node:fs:exists', path); +} + +export async function unlink(path: string) { + return ipcRenderer.invoke('node:fs:unlink', path); +} + +export async function stat(path: string) { + return ipcRenderer.invoke('node:fs:stat', path); +} + +export async function lstat(path: string) { + return ipcRenderer.invoke('node:fs:lstat', path); +} + +export async function realpath(path: string) { + return ipcRenderer.invoke('node:fs:realpath', path); +} + +export async function rename(path: string, newPath: string) { + return ipcRenderer.invoke('node:fs:rename', path, newPath); +} + +export async function chmod(path: string, mode: string | number) { + return ipcRenderer.invoke('node:fs:chmod', path, mode); +} + +export async function copyFile(from: string, to: string) { + return ipcRenderer.invoke('node:fs:copyFile', from, to); +} + +export async function copyFolder(from: string, to: string) { + return ipcRenderer.invoke('node:fs:copyFolder', from, to); +} + +export async function base64FromZip(path: string) { + return ipcRenderer.invoke('node:fs:base64FromZip', path); +} + +export async function setModifiedTime(path: string, time: Date) { + return ipcRenderer.invoke('node:fs:setModifiedTime', path, time); +} diff --git a/src-electron/preload/node-os.ts b/src-electron/preload/node-os.ts new file mode 100644 index 000000000..800e1714b --- /dev/null +++ b/src-electron/preload/node-os.ts @@ -0,0 +1,5 @@ +import { ipcRenderer } from 'electron'; + +export function homedir() { + return ipcRenderer.sendSync('node:os:homedir'); +} diff --git a/src-electron/preload/node-path.ts b/src-electron/preload/node-path.ts new file mode 100644 index 000000000..1750362b7 --- /dev/null +++ b/src-electron/preload/node-path.ts @@ -0,0 +1,23 @@ +import { ipcRenderer } from 'electron/renderer'; + +export function join(...paths: string[]) { + return ipcRenderer.sendSync('node:path:join', ...paths); +} + +export function basename(path: string) { + return ipcRenderer.sendSync('node:path:basename', path); +} + +export function resolve(...paths: string[]) { + return ipcRenderer.sendSync('node:path:resolve', ...paths); +} + +export function extname(...args: string[]) { + return ipcRenderer.sendSync('node:path:extname', ...args) +} +export function relative(...args: string[]) { + return ipcRenderer.sendSync('node:path:relative', ...args) +} +export function dirname(...args: string[]) { + return ipcRenderer.sendSync('node:path:dirname', ...args) +} diff --git a/src-electron/preload/zip-preload.ts b/src-electron/preload/zip-preload.ts new file mode 100644 index 000000000..462f5d3f0 --- /dev/null +++ b/src-electron/preload/zip-preload.ts @@ -0,0 +1,37 @@ +import { ipcRenderer } from 'electron'; + +export function extractAllTo(zip: string, outputFolder: string): Promise { + return ipcRenderer.invoke('zip:extractAllTo', zip, outputFolder); +} + +export function readFile(zip: string | Buffer, file: string): Promise { + return ipcRenderer.invoke('zip:readFile', zip, file); +} + +export function getEntries(zip: string): Promise { + return ipcRenderer.invoke('zip:getEntries', zip); +} + +export function extractEntryTo(zip: string | Buffer, target: string, outputPath: string): Promise { + return ipcRenderer.invoke('zip:extractEntryTo', zip, target, outputPath); +} + +/** + * Create a temporary in-memory zip. + * @return number representing identifier for subsequent calls to modify zip. + */ +export function createNewTemporaryZip(): number { + return ipcRenderer.sendSync(`zip:create:new`); +} + +export function addBufferToTemporaryZip(identifier: number, fileName: string, content: Buffer): Promise { + return ipcRenderer.invoke('zip:create:addBuffer', identifier, fileName, content); +} + +export function addFolderToTemporaryZip(identifier: number, zippedFolderName: string, folderNameOnDisk: string): Promise { + return ipcRenderer.invoke('zip:create:addFolder', identifier, zippedFolderName, folderNameOnDisk); +} + +export function finalizeTemporaryZip(identifier: number, outputPath: string): Promise { + return ipcRenderer.invoke('zip:create:finalize', identifier, outputPath); +} diff --git a/src-electron/main-process/window-state-persist.js b/src-electron/window-state-persist.ts similarity index 96% rename from src-electron/main-process/window-state-persist.js rename to src-electron/window-state-persist.ts index ed9feadd0..9897cd7b8 100644 --- a/src-electron/main-process/window-state-persist.js +++ b/src-electron/window-state-persist.ts @@ -1,10 +1,10 @@ -import * as yaml from 'yaml'; -import * as path from 'path'; -import * as fs from 'fs-extra'; +import yaml from 'yaml'; +import path from 'path'; +import fs from 'fs-extra'; let resizeTimeout = undefined; -export default class Persist { +export class Persist { static getSize(app, { defaultWidth, defaultHeight }) { const configFilePath = path.join(app.getPath('appData'), 'r2modmanPlus-local', 'config', 'window-state.yml'); diff --git a/src/App.vue b/src/App.vue index 473a2381e..1a53f7d6b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,9 +16,8 @@ import LogOutput from './r2mm/data/LogOutput'; import LogOutputProvider from './providers/ror2/data/LogOutputProvider'; import ThunderstoreDownloaderProvider from './providers/ror2/downloading/ThunderstoreDownloaderProvider'; import BetterThunderstoreDownloader from './r2mm/downloading/BetterThunderstoreDownloader'; -import { ipcRenderer } from 'electron'; import PathResolver from './r2mm/manager/PathResolver'; -import path from 'path'; +import path from './providers/node/path/path'; import ThemeManager from './r2mm/manager/ThemeManager'; import 'bulma-switch/dist/css/bulma-switch.min.css'; import LoggerProvider, { LogSeverity } from './providers/ror2/logging/LoggerProvider'; @@ -30,7 +29,6 @@ import FileUtils from './utils/FileUtils'; import LinkProvider from './providers/components/LinkProvider'; import LinkImpl from './r2mm/component_override/LinkImpl'; import FsProvider from './providers/generic/file/FsProvider'; -import NodeFs from './providers/generic/file/NodeFs'; import { DataFolderProvider } from './providers/ror2/system/DataFolderProvider'; import { DataFolderProviderImpl } from './r2mm/system/DataFolderProviderImpl'; import InteractionProvider from './providers/ror2/system/InteractionProvider'; @@ -47,13 +45,21 @@ import GenericProfileInstaller from './r2mm/installing/profile_installers/Generi import ErrorModal from './components/modals/ErrorModal.vue'; import { provideStoreImplementation } from './providers/generic/store/StoreProvider'; import baseStore from './store'; -import { getCurrentInstance, onMounted, ref, watchEffect } from 'vue'; +import { onMounted, ref, watchEffect } from 'vue'; import { useUtilityComposable } from './components/composables/UtilityComposable'; -import { Dark } from 'quasar'; - -const store = baseStore(); +import { useQuasar } from 'quasar'; +import { NodeFsImplementation } from './providers/node/fs/NodeFsImplementation'; +import { useRouter } from 'vue-router'; +import { ProtocolProviderImplementation } from './providers/generic/protocol/ProtocolProviderImplementation'; +import { provideProtocolImplementation } from './providers/generic/protocol/ProtocolProvider'; +import contextMenu from './providers/node/context_menu/context_menu'; + +const store = baseStore; +const router = useRouter(); provideStoreImplementation(() => store); +const quasar = useQuasar(); + document.addEventListener('auxclick', e => { const target = e.target! as any; if (target.localName == 'a') { @@ -70,7 +76,7 @@ const { const visible = ref(false); -FsProvider.provide(() => new NodeFs()); +FsProvider.provide(() => NodeFsImplementation); ProfileProvider.provide(() => new ProfileImpl()); LogOutputProvider.provide(() => LogOutput.getSingleton()); @@ -88,11 +94,11 @@ DataFolderProvider.provide(() => new DataFolderProviderImpl()); PlatformInterceptorProvider.provide(() => new PlatformInterceptorImpl()); +provideProtocolImplementation(() => ProtocolProviderImplementation) + BindLoaderImpl.bind(); onMounted(async () => { - // Load settings using the default game before the actual game is selected. - const router = getCurrentInstance()!.proxy.$router; const settings: ManagerSettings = await store.dispatch('resetActiveGame'); hookBackgroundUpdateThunderstoreModList(router); @@ -102,7 +108,7 @@ onMounted(async () => { InstallationRules.apply(); InstallationRules.validate(); - ipcRenderer.once('receive-appData-directory', async (_sender: any, appData: string) => { + window.app.getAppDataDirectory().then(async (appData: string) => { PathResolver.APPDATA_DIR = path.join(appData, 'r2modmanPlus-local'); // Legacy path. Needed for migration. PathResolver.CONFIG_DIR = path.join(PathResolver.APPDATA_DIR, "config"); @@ -123,21 +129,37 @@ onMounted(async () => { await FileUtils.ensureDirectory(PathResolver.APPDATA_DIR); await ThemeManager.apply(); - ipcRenderer.once('receive-is-portable', async (_sender: any, isPortable: boolean) => { + + window.app.isApplicationPortable().then((isPortable: boolean) => { ManagerInformation.IS_PORTABLE = isPortable; LoggerProvider.instance.Log(LogSeverity.INFO, `Starting manager on version ${ManagerInformation.VERSION.toString()}`); visible.value = true; }); - ipcRenderer.send('get-is-portable'); }); - ipcRenderer.send('get-appData-directory'); store.commit('updateModLoaderPackageNames'); store.dispatch('tsMods/updateExclusions'); }); watchEffect(() => { - document.documentElement.classList.toggle('html--dark', Dark.isActive); + document.documentElement.classList.toggle('html--dark', quasar.dark.isActive); +}); + +document.addEventListener('contextmenu', e => { + if (e.target) { + const target = e.target as HTMLElement; + switch (true) { + case target instanceof HTMLInputElement: { + contextMenu.showContextMenu({ readonly: false }); + break; + } + case ['code', 'pre'].includes(target.tagName.toLowerCase()): { + contextMenu.showContextMenu({ readonly: true }); + break; + } + default: { break; } + } + } }) diff --git a/src/AppWrapper.vue b/src/AppWrapper.vue index 3e909cfac..479eccf8a 100644 --- a/src/AppWrapper.vue +++ b/src/AppWrapper.vue @@ -1,9 +1,7 @@ diff --git a/src/components/navigation/NavigationMenu.vue b/src/components/navigation/NavigationMenu.vue index efd1636fb..3afe0c50d 100644 --- a/src/components/navigation/NavigationMenu.vue +++ b/src/components/navigation/NavigationMenu.vue @@ -61,7 +61,7 @@
- Select - {{ activeTab.toLowerCase() }} +
- Set as default +
@@ -102,12 +101,12 @@
-
- +
+
-
+
@@ -151,7 +150,7 @@