added initial files
@@ -0,0 +1,23 @@
|
|||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
/src_old
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/release
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es6": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"root": true,
|
||||||
|
"extends": ["airbnb", "prettier", "plugin:prettier/recommended"],
|
||||||
|
"globals": {
|
||||||
|
"Atomics": "readonly",
|
||||||
|
"SharedArrayBuffer": "readonly"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"alias": {
|
||||||
|
"map": [
|
||||||
|
["^ui$", "./src/ui"],
|
||||||
|
["app", "./src/app/"],
|
||||||
|
["common", "./src/common"]
|
||||||
|
],
|
||||||
|
"extensions": [".js", ".jsx", ".json", ".svg", ".png", ".jpg"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"prettier",
|
||||||
|
"compat",
|
||||||
|
"import",
|
||||||
|
"jsx-a11y",
|
||||||
|
"promise",
|
||||||
|
"react-hooks"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prettier/prettier": "error",
|
||||||
|
"jsx-a11y/aria-role": "off",
|
||||||
|
"no-restricted-syntax": "off",
|
||||||
|
"no-unused-vars": "error",
|
||||||
|
"no-unused-expressions": "error",
|
||||||
|
"consistent-return": "off",
|
||||||
|
"jsx-a11y/no-static-element-interactions": "off",
|
||||||
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-continue": "off",
|
||||||
|
"no-use-before-define": "off",
|
||||||
|
"no-await-in-loop": "off",
|
||||||
|
"default-param-last": "off",
|
||||||
|
"no-promise-executor-return": "off",
|
||||||
|
"import/no-import-module-exports": "off",
|
||||||
|
"react/destructuring-assignment": "off",
|
||||||
|
"promise/param-names": "error",
|
||||||
|
"promise/always-return": "error",
|
||||||
|
"promise/catch-or-return": "error",
|
||||||
|
"promise/no-native": "off",
|
||||||
|
"react/sort-comp": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"order": [
|
||||||
|
"type-annotations",
|
||||||
|
"static-methods",
|
||||||
|
"lifecycle",
|
||||||
|
"everything-else",
|
||||||
|
"render"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/jsx-no-bind": "off",
|
||||||
|
"react/jsx-filename-extension": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"react/function-component-definition": "off",
|
||||||
|
"react/forbid-dom-props": [2, { "forbid": ["style"] }],
|
||||||
|
"import/no-anonymous-default-export": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowArray": false,
|
||||||
|
"allowArrowFunction": false,
|
||||||
|
"allowAnonymousClass": false,
|
||||||
|
"allowAnonymousFunction": false,
|
||||||
|
"allowCallExpression": true,
|
||||||
|
"allowLiteral": false,
|
||||||
|
"allowObject": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"react/jsx-pascal-case": [2, { "allowAllCaps": false }],
|
||||||
|
"react/prefer-stateless-function": "off",
|
||||||
|
"import/no-unresolved": "off",
|
||||||
|
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||||
|
"jsx-a11y/anchor-is-valid": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
/data
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/release
|
||||||
|
/deploy
|
||||||
|
/public/native
|
||||||
|
|
||||||
|
# rust target
|
||||||
|
/target
|
||||||
|
/napi/target
|
||||||
|
/**/*/napi.node
|
||||||
|
/napi/napi.node
|
||||||
|
/napi/node_modules
|
||||||
|
/napi/index.d.ts
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.electron-builder.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env
|
||||||
|
.obsidian
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
signCertificate.pfx
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
_
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run lint --fix
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"arrow-parens": "off",
|
||||||
|
"consistent-return": "off",
|
||||||
|
"comma-dangle": "off",
|
||||||
|
"no-underscore-dangle": "off",
|
||||||
|
"generator-star-spacing": "off",
|
||||||
|
"no-shadow": "off",
|
||||||
|
"no-console": "off",
|
||||||
|
"no-use-before-define": "off",
|
||||||
|
"no-multi-assign": "off",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Copying
|
||||||
|
The source code of the GDLauncher repository is licensed under GNU General Public License Version 3, which is
|
||||||
|
provided in the file "LICENSE". However, graphic assets are licensed under a different license.
|
||||||
|
|
||||||
|
All graphic assets present in this repository are licensed as stated below in the "Assets License" section.
|
||||||
|
Some graphic assets such as the BisectHosting logo cannot be licensed by us but depending on your situation you might not be authorized to redistribute them.
|
||||||
|
|
||||||
|
## Assets License
|
||||||
|
All rights reserved.
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "murmurhash32"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://github.com/gorilla-devs/murmurhash32.git#401640895f352c88ccc61670993eec49c10e730b"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"murmurhash32",
|
||||||
|
"napi 2.5.0",
|
||||||
|
"napi-build",
|
||||||
|
"napi-derive",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec369bd2ae196a21bb08fb53761e637862c3c0699da9cd21b6d954e32dda04b3"
|
||||||
|
dependencies = [
|
||||||
|
"ctor",
|
||||||
|
"lazy_static",
|
||||||
|
"napi-sys",
|
||||||
|
"thread_local",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-build"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-derive"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c0308f2313ee504b032bad9c75343d967e6ba27be2e3f19e89bb0ee8786b5b0"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"napi-derive-backend",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-derive-backend"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c97606e865ebfca1c5d0c952e34969ca26b3c8ba0c246c569a2fc945719a78ea"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-sys"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67"
|
||||||
|
dependencies = [
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.39"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f53dc8cf16a769a6f677e09e7ff2cd4be1ea0f48754aac39520536962011de0d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.19.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||||
|
dependencies = [
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ko_fi: gdlauncher
|
||||||
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"napi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = 3
|
||||||
|
strip = "symbols"
|
||||||
|
lto = true
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
// const path = require("path");
|
||||||
|
|
||||||
|
// const alias = {
|
||||||
|
// app: path.resolve("./src/app/"),
|
||||||
|
// common: path.resolve("./src/common/"),
|
||||||
|
// ui: path.resolve("./src/ui/")
|
||||||
|
// };
|
||||||
|
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
|
||||||
|
const CracoAntDesignPlugin = require('craco-antd');
|
||||||
|
// const SentryCliPlugin = require('@sentry/webpack-plugin');
|
||||||
|
// const os = require('os');
|
||||||
|
// const packageJson = require('./package.json');
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
return {
|
||||||
|
babel: {
|
||||||
|
presets: [
|
||||||
|
[
|
||||||
|
'@babel/preset-env',
|
||||||
|
{
|
||||||
|
targets: {
|
||||||
|
node: '14'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'@babel/react'
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-class-properties',
|
||||||
|
'@babel/plugin-proposal-export-default-from',
|
||||||
|
'@babel/plugin-transform-runtime',
|
||||||
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
|
'@loadable/babel-plugin',
|
||||||
|
'babel-plugin-macros',
|
||||||
|
[
|
||||||
|
'babel-plugin-styled-components',
|
||||||
|
{
|
||||||
|
ssr: true,
|
||||||
|
pure: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
webpack: {
|
||||||
|
devtool: 'eval-cheap-module-source-map',
|
||||||
|
configure: webpackConfig => {
|
||||||
|
webpackConfig.target =
|
||||||
|
process.env.APP_TYPE === 'electron' ? 'electron-renderer' : 'web';
|
||||||
|
|
||||||
|
// webpackConfig.output = {
|
||||||
|
// filename: isEnvProduction
|
||||||
|
// ? 'static/js/[name].js'
|
||||||
|
// : isEnvDevelopment && 'static/js/bundle.js',
|
||||||
|
// chunkFilename: isEnvProduction
|
||||||
|
// ? 'static/js/[name].chunk.js'
|
||||||
|
// : isEnvDevelopment && 'static/js/[name].chunk.js'
|
||||||
|
// };
|
||||||
|
|
||||||
|
webpackConfig.optimization.splitChunks = {
|
||||||
|
name: false,
|
||||||
|
chunks: 'all',
|
||||||
|
maxInitialRequests: Infinity,
|
||||||
|
cacheGroups: {
|
||||||
|
vendor: {
|
||||||
|
test: /[\\/]node_modules[\\/]!(antd)/,
|
||||||
|
name(module) {
|
||||||
|
// get the name. E.g. node_modules/packageName/not/this/part.js
|
||||||
|
// or node_modules/packageName
|
||||||
|
const packageName = module.context.match(
|
||||||
|
/[\\/]node_modules[\\/](.*?)([\\/]|$)/
|
||||||
|
)[1];
|
||||||
|
|
||||||
|
// npm package names are URL-safe, but some servers don't like @ symbols
|
||||||
|
return `npm.${packageName.replace('@', '')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// if (process.env.SOURCE_MAPS_UPLOAD) {
|
||||||
|
// webpackConfig.plugins.push(
|
||||||
|
// new SentryCliPlugin({
|
||||||
|
// include: [
|
||||||
|
// './build/electron.js',
|
||||||
|
// './build/electron.js.map',
|
||||||
|
// './build/static/js/*'
|
||||||
|
// ],
|
||||||
|
// ignore: ['native'],
|
||||||
|
// org: 'gdlauncher',
|
||||||
|
// project: 'react',
|
||||||
|
// authToken: process.env.SENTRY_AUTH,
|
||||||
|
// url: process.env.SOURCE_MAPS_UPLOAD,
|
||||||
|
// release: packageJson.version,
|
||||||
|
// dist: `${process.env.REACT_APP_RELEASE_TYPE}-${os.platform()}`
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// console.log('Not a release. Skipping source maps upload.');
|
||||||
|
// }
|
||||||
|
|
||||||
|
webpackConfig.resolve.aliasFields = [];
|
||||||
|
webpackConfig.resolve.mainFields = ['module', 'main'];
|
||||||
|
webpackConfig.resolve.alias = {
|
||||||
|
...webpackConfig.resolve.alias,
|
||||||
|
react: 'preact/compat',
|
||||||
|
'react-dom/test-utils': 'preact/test-utils',
|
||||||
|
'react-dom': 'preact/compat', // Must be below test-utils
|
||||||
|
'react/jsx-runtime': 'preact/jsx-runtime'
|
||||||
|
};
|
||||||
|
|
||||||
|
return webpackConfig;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
plugin: CracoAntDesignPlugin
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
owner: gorilla-devs
|
||||||
|
repo: GDLauncher
|
||||||
|
provider: github
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>GDLauncher handler</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>gdlauncher</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,369 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ctor"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.126"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "murmurhash32"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "git+https://github.com/gorilla-devs/murmurhash32.git#401640895f352c88ccc61670993eec49c10e730b"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"futures",
|
||||||
|
"murmurhash32",
|
||||||
|
"napi 2.5.0",
|
||||||
|
"napi-build",
|
||||||
|
"napi-derive",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec369bd2ae196a21bb08fb53761e637862c3c0699da9cd21b6d954e32dda04b3"
|
||||||
|
dependencies = [
|
||||||
|
"ctor",
|
||||||
|
"lazy_static",
|
||||||
|
"napi-sys",
|
||||||
|
"thread_local",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-build"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebd4419172727423cf30351406c54f6cc1b354a2cfb4f1dba3e6cd07f6d5522b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-derive"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c0308f2313ee504b032bad9c75343d967e6ba27be2e3f19e89bb0ee8786b5b0"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"napi-derive-backend",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-derive-backend"
|
||||||
|
version = "1.0.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c97606e865ebfca1c5d0c952e34969ca26b3c8ba0c246c569a2fc945719a78ea"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "napi-sys"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "529671ebfae679f2ce9630b62dd53c72c56b3eb8b2c852e7e2fa91704ff93d67"
|
||||||
|
dependencies = [
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.19.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||||
|
dependencies = [
|
||||||
|
"num_cpus",
|
||||||
|
"once_cell",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "napi"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
napi = { version = "2", features = [
|
||||||
|
"tokio_fs",
|
||||||
|
"napi8",
|
||||||
|
"tokio_rt",
|
||||||
|
"async",
|
||||||
|
]}
|
||||||
|
futures = "0.3.21"
|
||||||
|
napi-derive = "2"
|
||||||
|
tokio = "1.19.2"
|
||||||
|
murmurhash32 = {git = "https://github.com/gorilla-devs/murmurhash32.git"}
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
napi-build = "1"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fn main() {
|
||||||
|
use napi_build::setup;
|
||||||
|
|
||||||
|
setup();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "napi",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"main": "./index.node",
|
||||||
|
"types": "./index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node ../node_modules/cross-env/src/bin/cross-env-shell.js node ../node_modules/@napi-rs/cli/scripts/index.js build --js true --release"
|
||||||
|
},
|
||||||
|
"napi": {
|
||||||
|
"name": "napi"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use murmurhash32::murmurhash2;
|
||||||
|
use napi::bindgen_prelude::*;
|
||||||
|
use napi_derive::napi;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
// Here for sentimental reasons.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[napi]
|
||||||
|
fn fibonacci(n: u32) -> u32 {
|
||||||
|
match n {
|
||||||
|
1 | 2 => 1,
|
||||||
|
_ => fibonacci(n - 1) + fibonacci(n - 2),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[napi]
|
||||||
|
async fn compute_path_murmur(path: String) -> Result<u32> {
|
||||||
|
let mut file = std::fs::File::open(path)?;
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
file.read_to_end(&mut buffer)?;
|
||||||
|
buffer.retain(|&x| (x != 9 && x != 10 && x != 13 && x != 32));
|
||||||
|
let hash = murmurhash2(&buffer);
|
||||||
|
Ok(hash)
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
{
|
||||||
|
"name": "gdlauncher",
|
||||||
|
"version": "1.1.30",
|
||||||
|
"description": "GDLauncher is simple, yet powerful Minecraft custom launcher with a strong focus on the user experience",
|
||||||
|
"keywords": [
|
||||||
|
"minecraft",
|
||||||
|
"launcher",
|
||||||
|
"gaming",
|
||||||
|
"games",
|
||||||
|
"react",
|
||||||
|
"redux",
|
||||||
|
"reload",
|
||||||
|
"sass",
|
||||||
|
"webpack"
|
||||||
|
],
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/gorilla-devs/GDLauncher/issues"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/gorilla-devs/GDLauncher.git"
|
||||||
|
},
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"author": {
|
||||||
|
"name": "GorillaDevs",
|
||||||
|
"email": "davide@gdevs.io",
|
||||||
|
"url": "https://gdevs.io"
|
||||||
|
},
|
||||||
|
"private": false,
|
||||||
|
"homepage": "./",
|
||||||
|
"main": "build/electron.js",
|
||||||
|
"scripts": {
|
||||||
|
"install": "npm run napi-build && npm run nsfw-move",
|
||||||
|
"start": "cross-env-shell NODE_ENV=development craco start",
|
||||||
|
"start-electron": "concurrently \"npm run start\" \"wait-on http://localhost:3000 && electron .\"",
|
||||||
|
"start-prod": "electron .",
|
||||||
|
"build-web": "cross-env-shell APP_TYPE=web craco build",
|
||||||
|
"build": "cross-env-shell NODE_ENV=production APP_TYPE=electron CI=false craco build",
|
||||||
|
"test": "craco test",
|
||||||
|
"eject": "craco eject",
|
||||||
|
"build-electron:portable": "cross-env-shell NODE_ENV=production REACT_APP_RELEASE_TYPE=portable webpack --config ./scripts/electronWebpackConfig.js --color",
|
||||||
|
"build-electron:setup": "cross-env-shell NODE_ENV=production REACT_APP_RELEASE_TYPE=setup webpack --config ./scripts/electronWebpackConfig.js --color",
|
||||||
|
"build-electron:dev": "cross-env-shell NODE_ENV=development REACT_APP_RELEASE_TYPE=setup webpack --config ./scripts/electronWebpackConfig.js --color",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"dev": "npm run build-electron:dev && cross-env-shell NODE_ENV=development APP_TYPE=electron BROWSER=none \"npm run start-electron\"",
|
||||||
|
"dev-web": "cross-env-shell NODE_ENV=development APP_TYPE=web npm run start",
|
||||||
|
"deploy": "node ./scripts/createDeploy.js",
|
||||||
|
"release:setup": "cross-env-shell NODE_ENV=production APP_TYPE=electron REACT_APP_RELEASE_TYPE=setup \"npm run build\" && npm run build-electron:setup && npm run deploy setup",
|
||||||
|
"release:portable": "cross-env-shell NODE_ENV=production APP_TYPE=electron REACT_APP_RELEASE_TYPE=portable \"npm run build\" && npm run build-electron:portable && npm run deploy portable",
|
||||||
|
"upload": "node ./scripts/uploadRelease.js",
|
||||||
|
"release": "npm run napi-build && rimraf ./deploy && npm run release:setup && npm run release:portable",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"napi-build": "cd napi && npm run build && node ../scripts/moveNapi.js",
|
||||||
|
"nsfw-move": "cd node_modules/nsfw && npm install && cd ../.. && node scripts/moveNsfw.js"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{js,jsx}": [
|
||||||
|
"cross-env NODE_ENV=development eslint --cache --format=pretty",
|
||||||
|
"prettier --ignore-path .eslintignore --single-quote --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"{*.json,.{babelrc,eslintrc,prettierrc,stylelintrc}}": [
|
||||||
|
"prettier --ignore-path .eslintignore --parser json --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.{css,scss}": [
|
||||||
|
"stylelint --ignore-path .eslintignore --syntax scss --fix",
|
||||||
|
"prettier --ignore-path .eslintignore --single-quote --write",
|
||||||
|
"git add"
|
||||||
|
],
|
||||||
|
"*.{yml,md}": [
|
||||||
|
"prettier --ignore-path .eslintignore --single-quote --write",
|
||||||
|
"git add"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16.0"
|
||||||
|
},
|
||||||
|
"cpu": [
|
||||||
|
"x64",
|
||||||
|
"arm64",
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ant-design/icons": "^4.7.0",
|
||||||
|
"@babel/cli": "^7.14.8",
|
||||||
|
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||||
|
"@babel/plugin-proposal-export-default-from": "^7.14.5",
|
||||||
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.15.0",
|
||||||
|
"@babel/preset-env": "^7.15.0",
|
||||||
|
"@babel/preset-react": "^7.14.5",
|
||||||
|
"@babel/register": "^7.15.3",
|
||||||
|
"@babel/runtime": "^7.15.3",
|
||||||
|
"@craco/craco": "^6.4.3",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.1.15",
|
||||||
|
"@napi-rs/cli": "^2.10.0",
|
||||||
|
"@sentry/webpack-plugin": "^1.18.3",
|
||||||
|
"babel-core": "^6.26.3",
|
||||||
|
"babel-loader": "^8.2.2",
|
||||||
|
"babel-plugin-macros": "^3.1.0",
|
||||||
|
"babel-plugin-module-resolver": "^4.1.0",
|
||||||
|
"babel-plugin-preval": "^5.0.0",
|
||||||
|
"babel-plugin-styled-components": "^1.13.2",
|
||||||
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||||
|
"babel-preset-minify": "^0.5.1",
|
||||||
|
"concurrently": "^6.2.1",
|
||||||
|
"cross-env": "^7.0.3",
|
||||||
|
"dotenv": "^10.0.0",
|
||||||
|
"electron": "^19.0.5",
|
||||||
|
"electron-builder": "^23.0.9",
|
||||||
|
"electron-updater": "^5.0.4",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
|
"eslint-config-prettier": "^8.3.0",
|
||||||
|
"eslint-formatter-pretty": "^4.1.0",
|
||||||
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
|
"eslint-import-resolver-webpack": "^0.13.2",
|
||||||
|
"eslint-plugin-compat": "^4.0.1",
|
||||||
|
"eslint-plugin-import": "^2.25.4",
|
||||||
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||||
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
|
"eslint-plugin-react": "^7.28.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.3.0",
|
||||||
|
"husky": "^7.0.4",
|
||||||
|
"native-ext-loader": "^2.3.0",
|
||||||
|
"nsfw": "2.1.2",
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"react-scripts": "4.0.3",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
|
"terser-webpack-plugin": "^4.1.0",
|
||||||
|
"wait-on": "^6.0.0",
|
||||||
|
"webpack": "^4.46.0",
|
||||||
|
"webpack-bundle-analyzer": "^4.4.2",
|
||||||
|
"webpack-cli": "^4.7.2",
|
||||||
|
"webpack-merge": "^5.8.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@loadable/babel-plugin": "^5.13.2",
|
||||||
|
"@sentry/integrations": "^6.16.1",
|
||||||
|
"@sentry/react": "^6.16.1",
|
||||||
|
"@sentry/tracing": "^6.16.1",
|
||||||
|
"@sentry/utils": "^6.16.1",
|
||||||
|
"7zip-bin": "^5.1.1",
|
||||||
|
"antd": "^4.21.5",
|
||||||
|
"axios": "^0.27.2",
|
||||||
|
"base64-stream": "^1.0.0",
|
||||||
|
"base64url": "^3.0.1",
|
||||||
|
"connected-react-router": "^6.9.2",
|
||||||
|
"craco-antd": "^1.19.0",
|
||||||
|
"directory-tree": "^2.3.1",
|
||||||
|
"discord-rpc": "^4.0.1",
|
||||||
|
"electron-log": "^4.4.5",
|
||||||
|
"electron-store": "^8.0.1",
|
||||||
|
"fast-xml-parser": "^4.0.1",
|
||||||
|
"fs-extra": "^10.1.0",
|
||||||
|
"get-folder-size": "^3.1.0",
|
||||||
|
"get-stream": "^6.0.1",
|
||||||
|
"he": "^1.2.0",
|
||||||
|
"history": "^4.10.1",
|
||||||
|
"inter-ui": "^3.19.3",
|
||||||
|
"is-electron": "^2.2.0",
|
||||||
|
"jimp": "^0.16.1",
|
||||||
|
"localforage": "^1.10.0",
|
||||||
|
"lockfile": "^1.0.4",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"make-dir": "^3.1.0",
|
||||||
|
"memoize-one": "^6.0.0",
|
||||||
|
"node-7z": "^3.0.0",
|
||||||
|
"node-machine-id": "^1.1.12",
|
||||||
|
"p-map": "^5.3.0",
|
||||||
|
"polished": "^4.1.3",
|
||||||
|
"preact": "^10.8.2",
|
||||||
|
"ps-tree": "^1.2.0",
|
||||||
|
"randomstring": "^1.2.2",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-beautiful-dnd": "^13.1.0",
|
||||||
|
"react-content-loader": "^6.0.3",
|
||||||
|
"react-contextmenu": "^2.14.0",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-html-parser": "^2.0.2",
|
||||||
|
"react-intersection-observer": "^8.33.1",
|
||||||
|
"react-markdown": "^7.1.1",
|
||||||
|
"react-portal": "^4.2.1",
|
||||||
|
"react-redux": "^7.2.6",
|
||||||
|
"react-router": "^5.2.0",
|
||||||
|
"react-router-dom": "^5.2.0",
|
||||||
|
"react-transition-group": "^4.4.2",
|
||||||
|
"react-type-animation": "^1.1.2",
|
||||||
|
"react-virtualized-auto-sizer": "^1.0.6",
|
||||||
|
"react-window": "^1.8.6",
|
||||||
|
"react-window-infinite-loader": "^1.0.7",
|
||||||
|
"redux": "^4.1.2",
|
||||||
|
"redux-logger": "^3.0.6",
|
||||||
|
"redux-persist": "^6.0.0",
|
||||||
|
"reselect": "^4.1.5",
|
||||||
|
"rooks": "^5.10.0",
|
||||||
|
"semver": "^7.3.5",
|
||||||
|
"slate": "^0.81.3",
|
||||||
|
"slate-history": "^0.65.3",
|
||||||
|
"slate-react": "^0.65.3",
|
||||||
|
"styled-components": "^5.3.0",
|
||||||
|
"symlink-dir": "^5.0.1",
|
||||||
|
"typeface-roboto": "^1.1.13",
|
||||||
|
"use-debounce": "^7.0.0",
|
||||||
|
"user-agents": "^1.0.1048",
|
||||||
|
"uuid": "^8.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
const { Client } = require('discord-rpc');
|
||||||
|
const log = require('electron-log');
|
||||||
|
|
||||||
|
let client;
|
||||||
|
let activity;
|
||||||
|
|
||||||
|
const initialAppStartup = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
const defaultValue = {
|
||||||
|
details: 'Idle',
|
||||||
|
startTimestamp: initialAppStartup,
|
||||||
|
largeImageKey: 'default_big',
|
||||||
|
largeImageText: 'GDLauncher - A Custom Minecraft Launcher',
|
||||||
|
instance: false
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.initRPC = () => {
|
||||||
|
client = new Client({ transport: 'ipc' });
|
||||||
|
|
||||||
|
activity = defaultValue;
|
||||||
|
|
||||||
|
client.on('ready', () => {
|
||||||
|
log.log('Discord RPC Connected');
|
||||||
|
client.setActivity(activity);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.login({ clientId: '555898932467597312' }).catch(error => {
|
||||||
|
if (error.message.includes('ENOENT')) {
|
||||||
|
log.error('Unable to initialize Discord RPC, no client detected.');
|
||||||
|
} else {
|
||||||
|
log.error('Unable to initialize Discord RPC:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.update = details => {
|
||||||
|
if (!client) return;
|
||||||
|
activity = {
|
||||||
|
...activity,
|
||||||
|
startTimestamp: Math.floor(Date.now() / 1000),
|
||||||
|
details: `Playing ${details}`
|
||||||
|
};
|
||||||
|
client.setActivity(activity);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.reset = () => {
|
||||||
|
if (!client) return;
|
||||||
|
activity = defaultValue;
|
||||||
|
activity.startTimestamp = initialAppStartup;
|
||||||
|
client.setActivity(activity);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.shutdownRPC = () => {
|
||||||
|
if (!client) return;
|
||||||
|
client.clearActivity();
|
||||||
|
client.destroy();
|
||||||
|
client = null;
|
||||||
|
activity = null;
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 26 KiB |
@@ -0,0 +1,226 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/icon.ico" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="GDLauncher -- A custom Minecraft launcher"
|
||||||
|
/>
|
||||||
|
<style>
|
||||||
|
.loader,
|
||||||
|
.loader:after {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 10em;
|
||||||
|
height: 10em;
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 60px auto;
|
||||||
|
position: relative;
|
||||||
|
text-indent: -9999em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader svg {
|
||||||
|
z-index: 2;
|
||||||
|
width: 700px;
|
||||||
|
animation: spin 1.1s infinite ease-in-out;
|
||||||
|
transform: scale(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0);
|
||||||
|
}
|
||||||
|
20% {
|
||||||
|
transform: rotate(-3.2deg);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
transform: rotate(370deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* @keyframes spin { 100% { transform:rotate(360deg); } } */
|
||||||
|
</style>
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
window.gaInitialized = true;
|
||||||
|
window.ga =
|
||||||
|
window.ga ||
|
||||||
|
function () {
|
||||||
|
(ga.q = ga.q || []).push(arguments);
|
||||||
|
};
|
||||||
|
ga.l = +new Date();
|
||||||
|
ga('create', 'UA-147631204-1', 'none');
|
||||||
|
ga('set', 'transport', 'xhr');
|
||||||
|
ga('set', 'appName', 'GDLauncher');
|
||||||
|
ga('set', 'checkProtocolTask', null);
|
||||||
|
ga('set', 'checkStorageTask', null);
|
||||||
|
ga('set', 'storage', 'none');
|
||||||
|
ga('set', 'storeGac', null);
|
||||||
|
ga('set', 'anonymizeIp', true);
|
||||||
|
ga('set', 'ds', 'app');
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
!(function () {
|
||||||
|
'use strict';
|
||||||
|
var i = {};
|
||||||
|
var a = window.location,
|
||||||
|
r = window.document,
|
||||||
|
o = r.currentScript,
|
||||||
|
l =
|
||||||
|
o.getAttribute('data-api') || 'https://plausible.io' + '/api/event';
|
||||||
|
function s(e) {
|
||||||
|
console.warn('Ignoring Event: ' + e);
|
||||||
|
}
|
||||||
|
function e(e, t) {
|
||||||
|
if (
|
||||||
|
/^localhost$|^127(\.[0-9]+){0,2}\.[0-9]+$|^\[::1?\]$/.test(
|
||||||
|
a.hostname
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return s('localhost');
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
window._phantom ||
|
||||||
|
window.__nightmare ||
|
||||||
|
window.navigator.webdriver ||
|
||||||
|
window.Cypress
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if ('true' === window.localStorage.plausible_ignore)
|
||||||
|
return s('localStorage flag');
|
||||||
|
} catch (e) {}
|
||||||
|
(i.n = e),
|
||||||
|
(i.d = 'app.gdevs.io'),
|
||||||
|
(i.r = 'https://app.gdevs.io'),
|
||||||
|
(i.w = window.innerWidth),
|
||||||
|
t && t.meta && (i.m = JSON.stringify(t.meta)),
|
||||||
|
t && t.props && (i.p = t.props),
|
||||||
|
(i.h = 1);
|
||||||
|
var n = new XMLHttpRequest();
|
||||||
|
n.open('POST', l, !0),
|
||||||
|
n.setRequestHeader('Content-Type', 'text/plain'),
|
||||||
|
n.send(JSON.stringify(i)),
|
||||||
|
(n.onreadystatechange = function () {
|
||||||
|
4 === n.readyState && t && t.callback && t.callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var t = (window.plausible && window.plausible.q) || [];
|
||||||
|
window.plausible = e;
|
||||||
|
for (var i, n = 0; n < t.length; n++) e.apply(this, t[n]);
|
||||||
|
function w() {
|
||||||
|
(i.u = 'https://app.gdevs.io' + a.hash.slice(1)),
|
||||||
|
e('pageview', { props: { version: '1.1.29' } });
|
||||||
|
}
|
||||||
|
window.addEventListener('hashchange', w),
|
||||||
|
'prerender' === r.visibilityState
|
||||||
|
? r.addEventListener('visibilitychange', function () {
|
||||||
|
i || 'visible' !== r.visibilityState || w();
|
||||||
|
})
|
||||||
|
: w();
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script async src="https://www.google-analytics.com/analytics.js"></script>
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>GDLauncher</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root">
|
||||||
|
<div
|
||||||
|
style="
|
||||||
|
width: 100%;
|
||||||
|
height: 95vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="loader">
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
viewBox="0 0 1080 1080"
|
||||||
|
style="enable-background: new 0 0 1080 1080"
|
||||||
|
xml:space="preserve"
|
||||||
|
>
|
||||||
|
<style type="text/css">
|
||||||
|
.st0 {
|
||||||
|
fill: #365076;
|
||||||
|
}
|
||||||
|
.st1 {
|
||||||
|
fill: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon
|
||||||
|
class="st0"
|
||||||
|
points="996,276.7 540,13.5 84,276.7 84,803.3 540,1066.5 996,803.3 "
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st1"
|
||||||
|
d="M540,797.5L540,797.5c77.7,0,140.7-63,140.7-140.7v-23.9c0-2,1.2-3.9,3.1-4.6
|
||||||
|
c21.8-8.8,150.6-66.2,133.6-193.2c-18.3-136.9-188.3-115.4-274-33.6c-1.9,1.8-4.9,1.8-6.9,0c-85.7-81.8-255.7-103.2-274,33.6
|
||||||
|
c-17,127,111.8,184.4,133.6,193.2c1.9,0.8,3.1,2.6,3.1,4.6v23.9C399.3,734.6,462.3,797.5,540,797.5L540,797.5L540,797.5z"
|
||||||
|
/>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M479.8,526.4c0.4,4.8-5.5,7.5-8.7,3.8c-10.3-12-30-29.1-62.9-38.2c-54-15,3.3-46.9,25.4-36.5
|
||||||
|
C453.6,465,475.6,478.1,479.8,526.4z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M600.2,526.4c-0.4,4.8,5.5,7.5,8.7,3.8c10.3-12,30-29.1,62.9-38.2c54-15-3.3-46.9-25.4-36.5
|
||||||
|
C626.4,465,604.4,478.1,600.2,526.4z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M571.8,598.1c7.4,16.4,19.2,49,8.9,72.1c-1.4,3.2-5.8,3.8-8.2,1.1c-8.4-9.5-23.6-33.4-10-72.7
|
||||||
|
C564,594.3,570,594,571.8,598.1z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="st0"
|
||||||
|
d="M508.2,598.1c-7.4,16.4-19.2,49-8.9,72.1c1.4,3.2,5.8,3.8,8.2,1.1c8.4-9.5,23.6-33.4,10-72.7
|
||||||
|
C516,594.3,510,594,508.2,598.1z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# For my application GUID (fix GUID to match what your application uses)
|
||||||
|
!macro customInit
|
||||||
|
# Workaround for installer handing when the app directory is removed manually
|
||||||
|
${ifNot} ${FileExists} "$INSTDIR"
|
||||||
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\{${APP_GUID}}"
|
||||||
|
|
||||||
|
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}" "QuietUninstallString"
|
||||||
|
StrCmp $0 "" proceed 0
|
||||||
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}"
|
||||||
|
proceed:
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro customUnInstall
|
||||||
|
push $4
|
||||||
|
push $0
|
||||||
|
|
||||||
|
SetShellVarContext current
|
||||||
|
ClearErrors
|
||||||
|
${ifNot} ${isUpdated}
|
||||||
|
|
||||||
|
MessageBox MB_YESNO "Also delete all your user data? (instances, preferences...)" IDNO Skipped IDYES Accepted
|
||||||
|
|
||||||
|
Accepted:
|
||||||
|
FileOpen $4 "$APPDATA\gdlauncher_next\override.data" r
|
||||||
|
FileRead $4 $0
|
||||||
|
RMDir /r "$0\instances"
|
||||||
|
RMDir /r "$0\java"
|
||||||
|
RMDir /r "$0\datastore"
|
||||||
|
RMDir /r "$0\blob_storage"
|
||||||
|
RMDir /r "$0\Local Storage"
|
||||||
|
RMDir /r "$0\Cache"
|
||||||
|
RMDir /r "$0\Code Cache"
|
||||||
|
RMDir /r "$0\Dictionaries"
|
||||||
|
RMDir /r "$0\GPUCache"
|
||||||
|
RMDir /r "$0\Session Storage"
|
||||||
|
RMDir /r "$0\shared_proto_db"
|
||||||
|
RMDir /r "$0\temp"
|
||||||
|
Delete "$0\Cookies"
|
||||||
|
Delete "$0\.updaterId"
|
||||||
|
Delete "$0\7za.exe"
|
||||||
|
Delete "$0\Cookies-journal"
|
||||||
|
Delete "$0\Network Persistent State"
|
||||||
|
Delete "$0\Preferences"
|
||||||
|
Delete "$0\TransportSecurity"
|
||||||
|
Delete "$0\Config"
|
||||||
|
Delete "$0\rChannel"
|
||||||
|
|
||||||
|
FileClose $4
|
||||||
|
|
||||||
|
RMDir /r "$APPDATA\gdlauncher_next"
|
||||||
|
Goto done
|
||||||
|
Skipped:
|
||||||
|
Goto done
|
||||||
|
done:
|
||||||
|
${endIf}
|
||||||
|
pop $4
|
||||||
|
pop $0
|
||||||
|
!macroend
|
||||||
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 896 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 669 B |
@@ -0,0 +1,123 @@
|
|||||||
|
/* eslint-disable global-require */
|
||||||
|
const { promises: fs } = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
// const os = require('os');
|
||||||
|
|
||||||
|
const NSFW = require('./native/nsfw.node');
|
||||||
|
|
||||||
|
function NSFWFilePoller(watchPath, eventCallback, debounceMS) {
|
||||||
|
const { CREATED, DELETED, MODIFIED } = nsfw.actions;
|
||||||
|
const directory = path.dirname(watchPath);
|
||||||
|
const file = path.basename(watchPath);
|
||||||
|
|
||||||
|
let fileStatus;
|
||||||
|
let filePollerInterval;
|
||||||
|
|
||||||
|
const getStatus = async () => {
|
||||||
|
try {
|
||||||
|
const status = await fs.stat(watchPath);
|
||||||
|
if (fileStatus === null) {
|
||||||
|
fileStatus = status;
|
||||||
|
eventCallback([{ action: CREATED, directory, file }]);
|
||||||
|
} else if (
|
||||||
|
status.mtime - fileStatus.mtime !== 0 ||
|
||||||
|
status.ctime - fileStatus.ctime !== 0
|
||||||
|
) {
|
||||||
|
fileStatus = status;
|
||||||
|
eventCallback([{ action: MODIFIED, directory, file }]);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (fileStatus !== null) {
|
||||||
|
fileStatus = null;
|
||||||
|
eventCallback([{ action: DELETED, directory, file }]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.start = async () => {
|
||||||
|
try {
|
||||||
|
fileStatus = await fs.stat(watchPath);
|
||||||
|
} catch (e) {
|
||||||
|
fileStatus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
filePollerInterval = setInterval(getStatus, debounceMS);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stop = async () => {
|
||||||
|
clearInterval(filePollerInterval);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pause = () => this.stop();
|
||||||
|
this.resume = () => this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildNSFW = async (
|
||||||
|
watchPath,
|
||||||
|
eventCallback,
|
||||||
|
{ debounceMS = 500, errorCallback: _errorCallback } = {}
|
||||||
|
) => {
|
||||||
|
if (Number.isInteger(debounceMS)) {
|
||||||
|
if (debounceMS < 1) {
|
||||||
|
throw new Error('Minimum debounce is 1ms.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('debounceMS must be an integer.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorCallback =
|
||||||
|
_errorCallback ||
|
||||||
|
(nsfwError => {
|
||||||
|
throw nsfwError;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!path.isAbsolute(watchPath)) {
|
||||||
|
throw new Error('Path to watch must be an absolute path.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let stats;
|
||||||
|
try {
|
||||||
|
stats = await fs.stat(watchPath);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Path must be a valid path to a file or a directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
return new NSFW(watchPath, eventCallback, { debounceMS, errorCallback });
|
||||||
|
}
|
||||||
|
if (stats.isFile()) {
|
||||||
|
return new NSFWFilePoller(watchPath, eventCallback, debounceMS);
|
||||||
|
}
|
||||||
|
throw new Error('Path must be a valid path to a file or a directory');
|
||||||
|
};
|
||||||
|
|
||||||
|
function nsfw(watchPath, eventCallback, options) {
|
||||||
|
if (!(this instanceof nsfw)) {
|
||||||
|
return buildNSFW(watchPath, eventCallback, options).then(
|
||||||
|
// eslint-disable-next-line
|
||||||
|
implementation => new nsfw(implementation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const implementation = watchPath;
|
||||||
|
|
||||||
|
this.start = () => implementation.start();
|
||||||
|
this.stop = () => implementation.stop();
|
||||||
|
this.pause = () => implementation.pause();
|
||||||
|
this.resume = () => implementation.resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
nsfw.actions = {
|
||||||
|
CREATED: 0,
|
||||||
|
DELETED: 1,
|
||||||
|
MODIFIED: 2,
|
||||||
|
RENAMED: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
nsfw._native = NSFW;
|
||||||
|
|
||||||
|
if (NSFW.getAllocatedInstanceCount) {
|
||||||
|
nsfw.getAllocatedInstanceCount = NSFW.getAllocatedInstanceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nsfw;
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
const { promisify } = require('util');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const zlib = require('zlib');
|
||||||
|
const makeDir = require('make-dir');
|
||||||
|
const { pipeline } = require('stream');
|
||||||
|
const fse = require('fs-extra');
|
||||||
|
const electronBuilder = require('electron-builder');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const readdir = promisify(fs.readdir);
|
||||||
|
const stat = promisify(fs.stat);
|
||||||
|
|
||||||
|
const type = process.argv[2];
|
||||||
|
|
||||||
|
const getFiles = async dir => {
|
||||||
|
const subdirs = await readdir(dir);
|
||||||
|
const files = await Promise.all(
|
||||||
|
subdirs.map(async subdir => {
|
||||||
|
const res = path.resolve(dir, subdir);
|
||||||
|
return (await stat(res)).isDirectory() ? getFiles(res) : res;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return files.reduce((a, f) => a.concat(f), []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSha1 = async filePath => {
|
||||||
|
// Calculate sha1 on original file
|
||||||
|
const algorithm = 'sha1';
|
||||||
|
const shasum = crypto.createHash(algorithm);
|
||||||
|
|
||||||
|
const s = fs.ReadStream(filePath);
|
||||||
|
s.on('data', data => {
|
||||||
|
shasum.update(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hash = await new Promise(resolve => {
|
||||||
|
s.on('end', () => {
|
||||||
|
resolve(shasum.digest('hex'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
const winReleaseFolder = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../',
|
||||||
|
'./release',
|
||||||
|
`win-unpacked`
|
||||||
|
);
|
||||||
|
const deployFolder = path.resolve(__dirname, '../', 'deploy');
|
||||||
|
|
||||||
|
const createDeployFiles = async () => {
|
||||||
|
const files = await getFiles(winReleaseFolder);
|
||||||
|
const mappedFiles = await Promise.all(
|
||||||
|
files.map(async v => {
|
||||||
|
// Compress
|
||||||
|
const hash = await getSha1(v);
|
||||||
|
|
||||||
|
const gzip = zlib.createGzip();
|
||||||
|
const source = fs.createReadStream(v);
|
||||||
|
|
||||||
|
const isAppAsar = path.basename(v) === 'app.asar';
|
||||||
|
|
||||||
|
const destinationPath = path.join(
|
||||||
|
deployFolder,
|
||||||
|
`win_${path.relative(winReleaseFolder, v).replace(path.sep, '-')}.gz`
|
||||||
|
);
|
||||||
|
await makeDir(path.dirname(destinationPath));
|
||||||
|
const destination = fs.createWriteStream(destinationPath);
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
pipeline(source, gzip, destination, err => {
|
||||||
|
if (err) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
destination.close();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const compressedSha1 = await getSha1(destinationPath);
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: path.relative(winReleaseFolder, v).split(path.sep),
|
||||||
|
sha1: hash,
|
||||||
|
compressedFile: path.basename(destinationPath),
|
||||||
|
compressedSha1,
|
||||||
|
...(isAppAsar && { isAppAsar: true })
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
path.join(deployFolder, `win32_latest.json`),
|
||||||
|
JSON.stringify(mappedFiles)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const extraFiles = [];
|
||||||
|
let sevenZipPath = null;
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
sevenZipPath = 'node_modules/7zip-bin/win/x64/7za.exe';
|
||||||
|
extraFiles.push({
|
||||||
|
from: 'vcredist/',
|
||||||
|
to: './',
|
||||||
|
filter: '**/*'
|
||||||
|
});
|
||||||
|
} else if (process.platform === 'linux') {
|
||||||
|
if (process.arch === 'arm64') {
|
||||||
|
sevenZipPath = 'node_modules/7zip-bin/linux/arm64/7za';
|
||||||
|
} else if (process.arch === 'arm') {
|
||||||
|
sevenZipPath = 'node_modules/7zip-bin/linux/arm/7za';
|
||||||
|
} else {
|
||||||
|
sevenZipPath = 'node_modules/7zip-bin/linux/x64/7za';
|
||||||
|
}
|
||||||
|
} else if (process.platform === 'darwin') {
|
||||||
|
sevenZipPath = 'node_modules/7zip-bin/mac/x64/7za';
|
||||||
|
}
|
||||||
|
|
||||||
|
extraFiles.push({
|
||||||
|
from: sevenZipPath,
|
||||||
|
to: './'
|
||||||
|
});
|
||||||
|
|
||||||
|
const commonConfig = {
|
||||||
|
publish: 'never',
|
||||||
|
config: {
|
||||||
|
generateUpdatesFilesForAllChannels: true,
|
||||||
|
npmRebuild: false,
|
||||||
|
productName: 'GDLauncher',
|
||||||
|
appId: 'org.gorilladevs.GDLauncher',
|
||||||
|
files: [
|
||||||
|
'!node_modules/**/*',
|
||||||
|
'build/**/*',
|
||||||
|
'package.json',
|
||||||
|
'public/icon.png'
|
||||||
|
],
|
||||||
|
extraFiles,
|
||||||
|
asar: {
|
||||||
|
smartUnpack: false
|
||||||
|
},
|
||||||
|
dmg: {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
x: 130,
|
||||||
|
y: 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: 410,
|
||||||
|
y: 220,
|
||||||
|
type: 'link',
|
||||||
|
path: '/Applications'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
nsisWeb: {
|
||||||
|
oneClick: true,
|
||||||
|
installerIcon: './public/icon.ico',
|
||||||
|
uninstallerIcon: './public/icon.ico',
|
||||||
|
installerHeader: './public/installerHeader.bmp',
|
||||||
|
installerSidebar: './public/installerSidebar.bmp',
|
||||||
|
installerHeaderIcon: './public/icon.ico',
|
||||||
|
deleteAppDataOnUninstall: true,
|
||||||
|
allowToChangeInstallationDirectory: false,
|
||||||
|
perMachine: false,
|
||||||
|
differentialPackage: true,
|
||||||
|
include: './public/installer.nsh'
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
entitlements: './entitlements.mac.plist',
|
||||||
|
entitlementsInherit: './entitlements.mac.plist'
|
||||||
|
},
|
||||||
|
/* eslint-disable */
|
||||||
|
artifactName: `${'${productName}'}-${'${os}'}-${
|
||||||
|
process.argv[2]
|
||||||
|
}.${'${ext}'}`,
|
||||||
|
/* eslint-enable */
|
||||||
|
linux: {
|
||||||
|
category: 'Game',
|
||||||
|
icon: 'public/linux-icons/'
|
||||||
|
},
|
||||||
|
directories: {
|
||||||
|
buildResources: 'public',
|
||||||
|
output: 'release'
|
||||||
|
},
|
||||||
|
protocols: [
|
||||||
|
{
|
||||||
|
name: 'gdlauncher',
|
||||||
|
role: 'Viewer',
|
||||||
|
schemes: ['gdlauncher']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
...(process.platform === 'linux' && {
|
||||||
|
linux:
|
||||||
|
type === 'setup'
|
||||||
|
? ['appimage', 'zip', 'deb', 'rpm'].map(x => `${x}:${process.arch}`)
|
||||||
|
: [`snap:${process.arch}`]
|
||||||
|
}),
|
||||||
|
...(process.platform === 'win32' && {
|
||||||
|
win: [type === 'setup' ? 'nsis:x64' : 'zip:x64']
|
||||||
|
}),
|
||||||
|
...(process.platform === 'darwin' && {
|
||||||
|
mac: type === 'setup' ? ['dmg:x64'] : []
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
const releasesFolder = path.resolve(__dirname, '../', './release');
|
||||||
|
await fse.remove(releasesFolder);
|
||||||
|
await makeDir(deployFolder);
|
||||||
|
await electronBuilder.build(commonConfig);
|
||||||
|
if (type === 'portable' && process.platform === 'win32') {
|
||||||
|
await createDeployFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy all other files to deploy folder
|
||||||
|
|
||||||
|
const { productName } = commonConfig.config;
|
||||||
|
|
||||||
|
let linuxyml = '';
|
||||||
|
if (process.arch === 'x64') {
|
||||||
|
linuxyml = 'latest-linux.yml';
|
||||||
|
} else {
|
||||||
|
linuxyml = `latest-linux-${process.arch}.yml`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allFiles = {
|
||||||
|
setup: {
|
||||||
|
darwin: [
|
||||||
|
`${productName}-mac-${type}.dmg`,
|
||||||
|
`${productName}-mac-${type}.dmg.blockmap`,
|
||||||
|
'latest-mac.yml'
|
||||||
|
],
|
||||||
|
win32: [
|
||||||
|
path.join(`${productName}-win-${type}.exe`),
|
||||||
|
path.join(`${productName}-win-${type}.exe.blockmap`),
|
||||||
|
path.join('latest.yml')
|
||||||
|
],
|
||||||
|
linux: [
|
||||||
|
`${productName}-linux-${type}.zip`,
|
||||||
|
`${productName}-linux-${type}.AppImage`,
|
||||||
|
`${productName}-linux-${type}.deb`,
|
||||||
|
`${productName}-linux-${type}.rpm`,
|
||||||
|
`${linuxyml}`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
portable: {
|
||||||
|
darwin: [],
|
||||||
|
win32: [`${productName}-win-${type}.zip`],
|
||||||
|
linux: [`${productName}-linux-${type}.snap`]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const filesToMove = allFiles[type][process.platform];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
filesToMove.map(async file => {
|
||||||
|
const stats = await fs.promises.stat(path.join(releasesFolder, file));
|
||||||
|
if (stats.isFile()) {
|
||||||
|
await fse.move(
|
||||||
|
path.join(releasesFolder, file),
|
||||||
|
path.join(deployFolder, file.replace('nsis-web', ''))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await fse.remove(releasesFolder);
|
||||||
|
};
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Webpack config for production electron main process
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const fse = require('fs-extra');
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const webpack = require('webpack');
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
|
|
||||||
|
fse.copySync(
|
||||||
|
path.resolve(__dirname, '../', 'public', 'native'),
|
||||||
|
path.resolve(__dirname, '../', 'build', 'native'),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const baseConfig = {
|
||||||
|
externals: [],
|
||||||
|
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.node$/,
|
||||||
|
loader: 'native-ext-loader',
|
||||||
|
options: {
|
||||||
|
basePath: ['native'],
|
||||||
|
emit: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.jsx?$/,
|
||||||
|
exclude: /node_modules/,
|
||||||
|
use: {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
cacheDirectory: true,
|
||||||
|
presets: [['@babel/preset-env', { targets: { node: '14' } }]],
|
||||||
|
plugins: [
|
||||||
|
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||||
|
'@babel/plugin-proposal-optional-chaining'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, '..', 'build'),
|
||||||
|
// https://github.com/webpack/webpack/issues/1114
|
||||||
|
libraryTarget: 'commonjs2'
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the array of extensions that should be used to resolve modules.
|
||||||
|
*/
|
||||||
|
resolve: {
|
||||||
|
extensions: ['.js', '.jsx', '.json'],
|
||||||
|
modules: [path.join(__dirname, '..', 'build'), 'node_modules']
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new webpack.EnvironmentPlugin({
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
REACT_APP_RELEASE_TYPE: process.env.REACT_APP_RELEASE_TYPE,
|
||||||
|
SENTRY_DSN: process.env.SENTRY_DSN
|
||||||
|
}),
|
||||||
|
new webpack.NamedModulesPlugin()
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = merge(baseConfig, {
|
||||||
|
devtool: 'source-map',
|
||||||
|
|
||||||
|
mode: process.env.NODE_ENV,
|
||||||
|
|
||||||
|
target: 'electron-main',
|
||||||
|
|
||||||
|
entry: './public/electron.js',
|
||||||
|
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, '..'),
|
||||||
|
filename: './build/electron.js'
|
||||||
|
},
|
||||||
|
|
||||||
|
optimization: {
|
||||||
|
minimizer: [
|
||||||
|
new TerserPlugin({
|
||||||
|
parallel: true,
|
||||||
|
sourceMap: true,
|
||||||
|
cache: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new BundleAnalyzerPlugin({
|
||||||
|
analyzerMode:
|
||||||
|
process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
|
||||||
|
openAnalyzer: process.env.OPEN_ANALYZER === 'true'
|
||||||
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create global constants which can be configured at compile time.
|
||||||
|
*
|
||||||
|
* Useful for allowing different behaviour between development builds and
|
||||||
|
* release builds
|
||||||
|
*
|
||||||
|
* NODE_ENV should be production so that modules do not perform certain
|
||||||
|
* development checks
|
||||||
|
*/
|
||||||
|
new webpack.EnvironmentPlugin({
|
||||||
|
NODE_ENV: 'production',
|
||||||
|
DEBUG_PROD: false,
|
||||||
|
START_MINIMIZED: false
|
||||||
|
})
|
||||||
|
],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables webpack processing of __dirname and __filename.
|
||||||
|
* If you run the bundle in node.js it falls back to these values of node.js.
|
||||||
|
* https://github.com/webpack/webpack/issues/2010
|
||||||
|
*/
|
||||||
|
node: {
|
||||||
|
__dirname: false,
|
||||||
|
__filename: false
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
// const os = require('os');
|
||||||
|
|
||||||
|
if (!fs.existsSync('../public/native')) fs.mkdirSync('../public/native');
|
||||||
|
|
||||||
|
fs.renameSync('./napi.node', `../public/native/napi.node`);
|
||||||
|
// fs.renameSync('./index.d.ts', `../public/native/${os.platform()}/index.d.ts`);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
if (!fs.existsSync('./public/native')) fs.mkdirSync('./public/native');
|
||||||
|
|
||||||
|
fs.copyFileSync(
|
||||||
|
'./node_modules/nsfw/build/Release/nsfw.node',
|
||||||
|
`./public/native/nsfw.node`
|
||||||
|
);
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
const { promisify } = require('util');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const axios = require('axios');
|
||||||
|
const fse = require('fs-extra');
|
||||||
|
const dotenv = require('dotenv');
|
||||||
|
const rawChangeLog = require('../src/common/modals/ChangeLogs/changeLog');
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const readdir = promisify(fs.readdir);
|
||||||
|
const stat = promisify(fs.stat);
|
||||||
|
|
||||||
|
const deployFolder = path.resolve(__dirname, '../', 'deploy');
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
if (!process.env.GH_ACCESS_TOKEN_RELEASES) {
|
||||||
|
console.warn('Cannot upload artifacts. No auth token provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { version } = await fse.readJson(
|
||||||
|
path.resolve(__dirname, '../', 'package.json')
|
||||||
|
);
|
||||||
|
|
||||||
|
let uploadUrl = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: releasesList } = await axios.default.get(
|
||||||
|
`https://api.github.com/repos/gorilla-devs/GDLauncher/releases`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${process.env.GH_ACCESS_TOKEN_RELEASES}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastRelease = releasesList.find(v => v.tag_name === `v${version}`);
|
||||||
|
|
||||||
|
if (lastRelease) {
|
||||||
|
uploadUrl = lastRelease.upload_url;
|
||||||
|
console.log('Found a release with this tag. Uploading there.');
|
||||||
|
} else {
|
||||||
|
throw new Error('Could not find release. Creating one.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
|
||||||
|
const getChangeLog = () => {
|
||||||
|
let changeLog = '';
|
||||||
|
for (const element in rawChangeLog) {
|
||||||
|
if (rawChangeLog[element].length) {
|
||||||
|
changeLog += `### ${element
|
||||||
|
.charAt(0)
|
||||||
|
.toUpperCase()}${element.substring(1)}\n`;
|
||||||
|
|
||||||
|
for (const e of rawChangeLog[element]) {
|
||||||
|
const prSplit = e?.advanced?.pr && e?.advanced?.pr.split('/');
|
||||||
|
const advanced =
|
||||||
|
e?.advanced?.cm &&
|
||||||
|
` ([${e?.advanced?.cm}](https://github.com/gorilla-devs/GDLauncher/commit/${e?.advanced?.cm})` +
|
||||||
|
`${
|
||||||
|
prSplit
|
||||||
|
? ` | [#${e?.advanced.pr}](https://github.com/gorilla-devs/GDLauncher/pull/${prSplit[0]}` +
|
||||||
|
`${prSplit?.[1] ? `/commits/${prSplit[1]}` : ''})`
|
||||||
|
: ''
|
||||||
|
})`;
|
||||||
|
const notes = `- **${e?.header || ''}** ${e?.content || ''}`;
|
||||||
|
changeLog += `${notes + advanced} \n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changeLog;
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data: newRelease } = await axios.default.post(
|
||||||
|
'https://api.github.com/repos/gorilla-devs/GDLauncher/releases',
|
||||||
|
{
|
||||||
|
tag_name: `v${version}`,
|
||||||
|
name: `v${version}`,
|
||||||
|
draft: true,
|
||||||
|
prerelease: version.includes('beta'),
|
||||||
|
body: getChangeLog()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${process.env.GH_ACCESS_TOKEN_RELEASES}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
uploadUrl = newRelease.upload_url;
|
||||||
|
console.log('New release tag created.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const deployFiles = await readdir(deployFolder);
|
||||||
|
|
||||||
|
console.log(`Found ${deployFiles.length} files to upload.`);
|
||||||
|
let uploaded = 0;
|
||||||
|
for (const file of deployFiles) {
|
||||||
|
const fileUploadUrl = uploadUrl.replace('{?name,label}', `?name=${file}`);
|
||||||
|
const stats = await stat(path.join(deployFolder, file));
|
||||||
|
const buffer = await fs.promises.readFile(path.join(deployFolder, file));
|
||||||
|
|
||||||
|
let contentType = null;
|
||||||
|
|
||||||
|
switch (path.extname(file)) {
|
||||||
|
case '.gz':
|
||||||
|
contentType = 'application/gzip';
|
||||||
|
break;
|
||||||
|
case '.zip':
|
||||||
|
contentType = 'application/zip';
|
||||||
|
break;
|
||||||
|
case '.json':
|
||||||
|
contentType = 'application/json';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
contentType = 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.default.post(fileUploadUrl, buffer, {
|
||||||
|
headers: {
|
||||||
|
'Content-Length': stats.size,
|
||||||
|
'Content-Type': contentType,
|
||||||
|
Authorization: `token ${process.env.GH_ACCESS_TOKEN_RELEASES}`
|
||||||
|
},
|
||||||
|
maxContentLength: Infinity,
|
||||||
|
maxBodyLength: Infinity
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.message);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
uploaded += 1;
|
||||||
|
console.log(`Uploaded ${uploaded} / ${deployFiles.length} -- ${file}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import isElectron from 'is-electron';
|
||||||
|
import DesktopRoot from './app/desktop/DesktopRoot';
|
||||||
|
import BrowserRoot from './app/browser/BrowserRoot';
|
||||||
|
|
||||||
|
const Root = isElectron() ? DesktopRoot : BrowserRoot;
|
||||||
|
|
||||||
|
export default Root;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Root from './app/desktop/DesktopRoot';
|
||||||
|
|
||||||
|
export default Root;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Root from './app/browser/BrowserRoot';
|
||||||
|
|
||||||
|
export default Root;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function BrowserRoot() {
|
||||||
|
return <div>This is a browser app</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BrowserRoot;
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import React, { useEffect, memo } from 'react';
|
||||||
|
import { useDidMount } from 'rooks';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Switch } from 'react-router';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import { message } from 'antd';
|
||||||
|
import RouteWithSubRoutes from '../../common/components/RouteWithSubRoutes';
|
||||||
|
import {
|
||||||
|
loginWithAccessToken,
|
||||||
|
initManifests,
|
||||||
|
initNews,
|
||||||
|
loginThroughNativeLauncher,
|
||||||
|
switchToFirstValidAccount,
|
||||||
|
checkClientToken,
|
||||||
|
updateUserData,
|
||||||
|
loginWithOAuthAccessToken
|
||||||
|
} from '../../common/reducers/actions';
|
||||||
|
import {
|
||||||
|
load,
|
||||||
|
received,
|
||||||
|
requesting
|
||||||
|
} from '../../common/reducers/loading/actions';
|
||||||
|
import features from '../../common/reducers/loading/features';
|
||||||
|
import GlobalStyles from '../../common/GlobalStyles';
|
||||||
|
import RouteBackground from '../../common/components/RouteBackground';
|
||||||
|
import ga from '../../common/utils/analytics';
|
||||||
|
import routes from './utils/routes';
|
||||||
|
import { _getCurrentAccount } from '../../common/utils/selectors';
|
||||||
|
import { isLatestJavaDownloaded } from './utils';
|
||||||
|
import SystemNavbar from './components/SystemNavbar';
|
||||||
|
import useTrackIdle from './utils/useTrackIdle';
|
||||||
|
import { openModal } from '../../common/reducers/modals/actions';
|
||||||
|
import Message from './components/Message';
|
||||||
|
import {
|
||||||
|
ACCOUNT_MICROSOFT,
|
||||||
|
LATEST_JAVA_VERSION
|
||||||
|
} from '../../common/utils/constants';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: ${props => props.theme.sizes.height.systemNavbar}px;
|
||||||
|
height: calc(100vh - ${props => props.theme.sizes.height.systemNavbar}px);
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
will-change: transform;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function DesktopRoot({ store }) {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const currentAccount = useSelector(_getCurrentAccount);
|
||||||
|
const clientToken = useSelector(state => state.app.clientToken);
|
||||||
|
const javaPath = useSelector(state => state.settings.java.path);
|
||||||
|
const javaLatestPath = useSelector(state => state.settings.java.pathLatest);
|
||||||
|
const location = useSelector(state => state.router.location);
|
||||||
|
// const modals = useSelector(state => state.modals);
|
||||||
|
const shouldShowDiscordRPC = useSelector(state => state.settings.discordRPC);
|
||||||
|
// const [contentStyle, setContentStyle] = useState({ transform: 'scale(1)' });
|
||||||
|
|
||||||
|
message.config({
|
||||||
|
top: 45,
|
||||||
|
maxCount: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
dispatch(requesting(features.mcAuthentication));
|
||||||
|
const userDataStatic = await ipcRenderer.invoke('getUserData');
|
||||||
|
const userData = dispatch(updateUserData(userDataStatic));
|
||||||
|
await dispatch(checkClientToken());
|
||||||
|
dispatch(initNews());
|
||||||
|
|
||||||
|
const manifests = await dispatch(initManifests());
|
||||||
|
|
||||||
|
let isJava8OK = javaPath;
|
||||||
|
let isJavaLatestOk = javaLatestPath;
|
||||||
|
|
||||||
|
if (!javaPath) {
|
||||||
|
({ isValid: isJava8OK } = await isLatestJavaDownloaded(
|
||||||
|
manifests,
|
||||||
|
userData,
|
||||||
|
true
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isJavaLatestOk) {
|
||||||
|
({ isValid: isJavaLatestOk } = await isLatestJavaDownloaded(
|
||||||
|
manifests,
|
||||||
|
userData,
|
||||||
|
true,
|
||||||
|
LATEST_JAVA_VERSION
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isJava8OK || !isJavaLatestOk) {
|
||||||
|
dispatch(openModal('JavaSetup', { preventClose: true }));
|
||||||
|
|
||||||
|
// Super duper hacky solution to await the modal to be closed...
|
||||||
|
// Please forgive me
|
||||||
|
await new Promise(resolve => {
|
||||||
|
function checkModalStillOpen(state) {
|
||||||
|
return state.modals.find(v => v.modalType === 'JavaSetup');
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentValue;
|
||||||
|
const unsubscribe = store.subscribe(() => {
|
||||||
|
const previousValue = currentValue;
|
||||||
|
currentValue = store.getState().modals.length;
|
||||||
|
if (previousValue !== currentValue) {
|
||||||
|
const stillOpen = checkModalStillOpen(store.getState());
|
||||||
|
|
||||||
|
if (!stillOpen) {
|
||||||
|
unsubscribe();
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development' && currentAccount) {
|
||||||
|
dispatch(received(features.mcAuthentication));
|
||||||
|
dispatch(push('/home'));
|
||||||
|
} else if (currentAccount) {
|
||||||
|
dispatch(
|
||||||
|
load(
|
||||||
|
features.mcAuthentication,
|
||||||
|
dispatch(
|
||||||
|
currentAccount.accountType === ACCOUNT_MICROSOFT
|
||||||
|
? loginWithOAuthAccessToken()
|
||||||
|
: loginWithAccessToken()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).catch(() => {
|
||||||
|
dispatch(switchToFirstValidAccount());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
load(features.mcAuthentication, dispatch(loginThroughNativeLauncher()))
|
||||||
|
).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldShowDiscordRPC) {
|
||||||
|
ipcRenderer.invoke('init-discord-rpc');
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcRenderer.on('custom-protocol-event', (e, data) => {
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle already logged in account redirect
|
||||||
|
useDidMount(init);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentAccount) {
|
||||||
|
dispatch(push('/'));
|
||||||
|
}
|
||||||
|
}, [currentAccount]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (clientToken && process.env.NODE_ENV !== 'development') {
|
||||||
|
ga.setUserId(clientToken);
|
||||||
|
ga.trackPage(location.pathname);
|
||||||
|
}
|
||||||
|
}, [location.pathname, clientToken]);
|
||||||
|
|
||||||
|
useTrackIdle(location.pathname);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (
|
||||||
|
// modals[0] &&
|
||||||
|
// modals[0].modalType === 'Settings' &&
|
||||||
|
// !modals[0].unmounting
|
||||||
|
// ) {
|
||||||
|
// setContentStyle({ transform: 'scale(0.4)' });
|
||||||
|
// } else {
|
||||||
|
// setContentStyle({ transform: 'scale(1)' });
|
||||||
|
// }
|
||||||
|
// }, [modals]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
<SystemNavbar />
|
||||||
|
<Message />
|
||||||
|
<Container>
|
||||||
|
<GlobalStyles />
|
||||||
|
<RouteBackground />
|
||||||
|
<Switch>
|
||||||
|
{routes.map((route, i) => (
|
||||||
|
<RouteWithSubRoutes key={i} {...route} /> // eslint-disable-line
|
||||||
|
))}
|
||||||
|
</Switch>
|
||||||
|
</Container>
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(DesktopRoot);
|
||||||
@@ -0,0 +1,466 @@
|
|||||||
|
import React, { useState, useEffect, memo } from 'react';
|
||||||
|
import { transparentize } from 'polished';
|
||||||
|
import styled, { keyframes } from 'styled-components';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
import path from 'path';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { Portal } from 'react-portal';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import {
|
||||||
|
faPlay,
|
||||||
|
faClock,
|
||||||
|
faWrench,
|
||||||
|
faFolder,
|
||||||
|
faTrash,
|
||||||
|
faStop,
|
||||||
|
faBoxOpen,
|
||||||
|
faCopy,
|
||||||
|
faServer,
|
||||||
|
faHammer
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import psTree from 'ps-tree';
|
||||||
|
import { ContextMenuTrigger, ContextMenu, MenuItem } from 'react-contextmenu';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import {
|
||||||
|
_getInstance,
|
||||||
|
_getInstancesPath,
|
||||||
|
_getDownloadQueue
|
||||||
|
} from '../../../../common/utils/selectors';
|
||||||
|
import {
|
||||||
|
addStartedInstance,
|
||||||
|
addToQueue,
|
||||||
|
launchInstance
|
||||||
|
} from '../../../../common/reducers/actions';
|
||||||
|
import { openModal } from '../../../../common/reducers/modals/actions';
|
||||||
|
import instanceDefaultBackground from '../../../../common/assets/instance_default.png';
|
||||||
|
import { convertMinutesToHumanTime } from '../../../../common/utils';
|
||||||
|
import { FABRIC, FORGE, VANILLA } from '../../../../common/utils/constants';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 180px;
|
||||||
|
height: 100px;
|
||||||
|
transform: ${p =>
|
||||||
|
p.isHovered && !p.installing
|
||||||
|
? 'scale3d(1.1, 1.1, 1.1)'
|
||||||
|
: 'scale3d(1, 1, 1)'};
|
||||||
|
margin-right: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
transition: transform 150ms ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
${p => (p.installing ? '' : 'transform: scale3d(1.1, 1.1, 1.1);')}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Spinner = keyframes`
|
||||||
|
0% {
|
||||||
|
transform: translate3d(-50%, -50%, 0) rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translate3d(-50%, -50%, 0) rotate(360deg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PlayButtonAnimation = keyframes`
|
||||||
|
from {
|
||||||
|
transform: scale(0.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InstanceContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
position: absolute;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(0deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),
|
||||||
|
url('${props => props.background}') center no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
color: ${props => props.theme.palette.text.secondary};
|
||||||
|
font-weight: 600;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const HoverContainer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 800;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: opacity 150ms ease-in-out;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: ${p => (p.installing || p.isHovered ? '1' : '0')};
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
will-change: opacity;
|
||||||
|
background: ${p => transparentize(0.5, p.theme.palette.grey[800])};
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner:before {
|
||||||
|
animation: 1.5s linear infinite ${Spinner};
|
||||||
|
animation-play-state: inherit;
|
||||||
|
border: solid 3px transparent;
|
||||||
|
border-bottom-color: ${props => props.theme.palette.colors.yellow};
|
||||||
|
border-radius: 50%;
|
||||||
|
content: '';
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MCVersion = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: ${props => props.theme.palette.text.third};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TimePlayed = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
top: 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: ${props => props.theme.palette.text.third};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MenuInstanceName = styled.div`
|
||||||
|
background: ${props => props.theme.palette.grey[800]};
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
padding: 0 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Instance = ({ instanceName }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
const [background, setBackground] = useState(`${instanceDefaultBackground}`);
|
||||||
|
const instance = useSelector(state => _getInstance(state)(instanceName));
|
||||||
|
const downloadQueue = useSelector(_getDownloadQueue);
|
||||||
|
const currentDownload = useSelector(state => state.currentDownload);
|
||||||
|
const startedInstances = useSelector(state => state.startedInstances);
|
||||||
|
const instancesPath = useSelector(_getInstancesPath);
|
||||||
|
const isInQueue = downloadQueue[instanceName];
|
||||||
|
|
||||||
|
const isPlaying = startedInstances[instanceName];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (instance.background) {
|
||||||
|
fs.readFile(path.join(instancesPath, instanceName, instance.background))
|
||||||
|
.then(res =>
|
||||||
|
setBackground(`data:image/png;base64,${res.toString('base64')}`)
|
||||||
|
)
|
||||||
|
.catch(console.warning);
|
||||||
|
} else {
|
||||||
|
setBackground(`${instanceDefaultBackground}`);
|
||||||
|
}
|
||||||
|
}, [instance.background, instancesPath, instanceName]);
|
||||||
|
|
||||||
|
const startInstance = () => {
|
||||||
|
if (isInQueue || isPlaying) return;
|
||||||
|
dispatch(addStartedInstance({ instanceName }));
|
||||||
|
dispatch(launchInstance(instanceName));
|
||||||
|
};
|
||||||
|
const openFolder = () => {
|
||||||
|
ipcRenderer.invoke('openFolder', path.join(instancesPath, instance.name));
|
||||||
|
};
|
||||||
|
const openConfirmationDeleteModal = () => {
|
||||||
|
dispatch(openModal('InstanceDeleteConfirmation', { instanceName }));
|
||||||
|
};
|
||||||
|
const manageInstance = () => {
|
||||||
|
dispatch(openModal('InstanceManager', { instanceName }));
|
||||||
|
};
|
||||||
|
const openBisectModal = () => {
|
||||||
|
dispatch(openModal('BisectHosting'));
|
||||||
|
};
|
||||||
|
const instanceExportCurseForge = () => {
|
||||||
|
dispatch(openModal('InstanceExportCurseForge', { instanceName }));
|
||||||
|
};
|
||||||
|
const openDuplicateNameDialog = () => {
|
||||||
|
dispatch(openModal('InstanceDuplicateName', { instanceName }));
|
||||||
|
};
|
||||||
|
const killProcess = () => {
|
||||||
|
psTree(isPlaying.pid, (err, children) => {
|
||||||
|
process.kill(isPlaying.pid);
|
||||||
|
if (children?.length) {
|
||||||
|
children.forEach(el => {
|
||||||
|
if (el) {
|
||||||
|
try {
|
||||||
|
process.kill(el.PID);
|
||||||
|
} catch {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
process.kill(el.PPID);
|
||||||
|
} catch {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
process.kill(isPlaying.pid);
|
||||||
|
} catch {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContextMenuTrigger id={instanceName}>
|
||||||
|
<Container
|
||||||
|
installing={isInQueue}
|
||||||
|
onClick={startInstance}
|
||||||
|
isHovered={isHovered || isPlaying}
|
||||||
|
>
|
||||||
|
<InstanceContainer installing={isInQueue} background={background}>
|
||||||
|
<TimePlayed>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faClock}
|
||||||
|
css={`
|
||||||
|
margin-right: 5px;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{convertMinutesToHumanTime(instance.timePlayed)}
|
||||||
|
</TimePlayed>
|
||||||
|
<MCVersion>{instance.loader?.mcVersion}</MCVersion>
|
||||||
|
{instanceName}
|
||||||
|
</InstanceContainer>
|
||||||
|
<HoverContainer
|
||||||
|
installing={isInQueue}
|
||||||
|
isHovered={isHovered || isPlaying}
|
||||||
|
>
|
||||||
|
{currentDownload === instanceName ? (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 14px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{isInQueue ? isInQueue.status : null}
|
||||||
|
</div>
|
||||||
|
{`${isInQueue.percentage}%`}
|
||||||
|
<LoadingOutlined
|
||||||
|
css={`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
right: 8px;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{isPlaying && (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{isPlaying.initialized && (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
css={`
|
||||||
|
color: ${({ theme }) => theme.palette.colors.green};
|
||||||
|
font-size: 27px;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -6px;
|
||||||
|
margin-top: -2px;
|
||||||
|
animation: ${PlayButtonAnimation} 0.5s
|
||||||
|
cubic-bezier(0.75, -1.5, 0, 2.75);
|
||||||
|
`}
|
||||||
|
icon={faPlay}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!isPlaying.initialized && <div className="spinner" />}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isInQueue && 'In Queue'}
|
||||||
|
{!isInQueue && !isPlaying && <span>PLAY</span>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</HoverContainer>
|
||||||
|
</Container>
|
||||||
|
</ContextMenuTrigger>
|
||||||
|
<Portal>
|
||||||
|
<ContextMenu
|
||||||
|
id={instance.name}
|
||||||
|
onShow={() => setIsHovered(true)}
|
||||||
|
onHide={() => setIsHovered(false)}
|
||||||
|
>
|
||||||
|
<MenuInstanceName>{instanceName}</MenuInstanceName>
|
||||||
|
{isPlaying && (
|
||||||
|
<MenuItem onClick={killProcess}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faStop}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Kill
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
<MenuItem disabled={Boolean(isInQueue)} onClick={manageInstance}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faWrench}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Manage
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem onClick={openFolder}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faFolder}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Open Folder
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{/* // TODO - Support other export options besides curseforge forge. */}
|
||||||
|
<MenuItem
|
||||||
|
onClick={instanceExportCurseForge}
|
||||||
|
disabled={
|
||||||
|
Boolean(isInQueue) ||
|
||||||
|
!(
|
||||||
|
instance.loader?.loaderType === FORGE ||
|
||||||
|
instance.loader?.loaderType === FABRIC ||
|
||||||
|
instance.loader?.loaderType === VANILLA
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faBoxOpen}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Export Pack
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={Boolean(isInQueue)}
|
||||||
|
onClick={openDuplicateNameDialog}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCopy}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Duplicate
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem divider />
|
||||||
|
<MenuItem
|
||||||
|
disabled={Boolean(isInQueue) || Boolean(isPlaying)}
|
||||||
|
onClick={async () => {
|
||||||
|
let manifest = null;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(
|
||||||
|
await fs.readFile(
|
||||||
|
path.join(instancesPath, instanceName, 'manifest.json')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// NO-OP
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToQueue(
|
||||||
|
instanceName,
|
||||||
|
instance.loader,
|
||||||
|
manifest,
|
||||||
|
instance.background,
|
||||||
|
instance.timePlayed,
|
||||||
|
{},
|
||||||
|
{ isUpdate: true }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faHammer}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Repair
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={Boolean(isInQueue) || Boolean(isPlaying)}
|
||||||
|
onClick={openConfirmationDeleteModal}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faTrash}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Delete
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem divider />
|
||||||
|
<MenuItem
|
||||||
|
onClick={openBisectModal}
|
||||||
|
preventClose
|
||||||
|
css={`
|
||||||
|
border: 2px solid #04cbeb;
|
||||||
|
border-radius: 5px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faServer}
|
||||||
|
css={`
|
||||||
|
margin-right: 10px;
|
||||||
|
width: 25px !important;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
Create Server
|
||||||
|
</MenuItem>
|
||||||
|
</ContextMenu>
|
||||||
|
</Portal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Instance);
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { memo, useMemo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { _getInstances } from '../../../../common/utils/selectors';
|
||||||
|
import Instance from './Instance';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const NoInstance = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 25px;
|
||||||
|
margin-top: 100px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubNoInstance = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: 20px;
|
||||||
|
`;
|
||||||
|
const sortAlphabetical = instances =>
|
||||||
|
instances.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||||
|
|
||||||
|
const sortByLastPlayed = instances =>
|
||||||
|
instances.sort((a, b) => (a.lastPlayed < b.lastPlayed ? 1 : -1));
|
||||||
|
|
||||||
|
const sortByMostPlayed = instances =>
|
||||||
|
instances.sort((a, b) => (a.timePlayed < b.timePlayed ? 1 : -1));
|
||||||
|
|
||||||
|
const getInstances = (instances, sortOrder) => {
|
||||||
|
// Data normalization for missing fields
|
||||||
|
const inst = instances.map(instance => {
|
||||||
|
return {
|
||||||
|
...instance,
|
||||||
|
timePlayed: instance.timePlayed || 0,
|
||||||
|
lastPlayed: instance.lastPlayed || 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (sortOrder) {
|
||||||
|
case 0:
|
||||||
|
return sortAlphabetical(inst);
|
||||||
|
case 1:
|
||||||
|
return sortByLastPlayed(inst);
|
||||||
|
case 2:
|
||||||
|
return sortByMostPlayed(inst);
|
||||||
|
default:
|
||||||
|
return inst;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Instances = () => {
|
||||||
|
const instanceSortOrder = useSelector(
|
||||||
|
state => state.settings.instanceSortOrder
|
||||||
|
);
|
||||||
|
const instances = useSelector(_getInstances);
|
||||||
|
const memoInstances = useMemo(
|
||||||
|
() => getInstances(instances || [], instanceSortOrder),
|
||||||
|
[instances, instanceSortOrder]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{memoInstances.length > 0 ? (
|
||||||
|
memoInstances.map(i => <Instance key={i.name} instanceName={i.name} />)
|
||||||
|
) : (
|
||||||
|
<NoInstance>
|
||||||
|
No Instance has been installed
|
||||||
|
<SubNoInstance>
|
||||||
|
Click on the icon in the bottom left corner to add new instances
|
||||||
|
</SubNoInstance>
|
||||||
|
</NoInstance>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Instances);
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import React, { useState, useEffect, memo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { LoadingOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const MessageContainer = styled.div`
|
||||||
|
width: 280px;
|
||||||
|
height: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: 50px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
background: ${props => props.theme.palette.grey[800]};
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
z-index: 10000000;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: opacity 200ms ease-in-out;
|
||||||
|
opacity: ${props => props.visible};
|
||||||
|
visibility: ${props => (props.visible ? 'visible' : 'hidden')};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Message = () => {
|
||||||
|
const currentState = useSelector(state => state.message);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!currentState) {
|
||||||
|
setVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible(true);
|
||||||
|
if (currentState.duration !== 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setVisible(false);
|
||||||
|
}, currentState.duration);
|
||||||
|
}
|
||||||
|
}, [currentState]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageContainer visible={visible ? 1 : 0}>
|
||||||
|
{currentState?.content} <LoadingOutlined />
|
||||||
|
</MessageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Message);
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import React, { useState, useEffect, useRef, useContext } from 'react';
|
||||||
|
import ContentLoader from 'react-content-loader';
|
||||||
|
import styled, { ThemeContext } from 'styled-components';
|
||||||
|
import { shell } from 'electron';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
const Carousel = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: ${props => props.theme.shape.borderRadius};
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ImageSlider = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
object-fit: covert;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: ${props => props.theme.shape.borderRadius};
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
margin: 0 auto 0 auto;
|
||||||
|
width: 1000%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
transform: translate(${props => `${props.currentImageIndex}px`});
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// padding: 200px;
|
||||||
|
const ImageSlide = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: ${props => props.theme.shape.borderRadius};
|
||||||
|
background-image: url('${props => (props.image ? props.image : null)}');
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
z-index: -1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Slide = styled.div`
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 0;
|
||||||
|
&:hover ${ImageSlide} {
|
||||||
|
transform: scale(1.06);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Gradient = styled.div`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: ${props => props.theme.shape.borderRadius};
|
||||||
|
background-image: linear-gradient(
|
||||||
|
0deg,
|
||||||
|
rgba(0, 0, 0, 1) 0%,
|
||||||
|
rgba(165, 165, 165, 0) 80%
|
||||||
|
);
|
||||||
|
opacity: 0.99;
|
||||||
|
z-index: 1;
|
||||||
|
&&:hover {
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Select = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
top: 160px;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -100px;
|
||||||
|
padding: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 5px;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectElement = styled.div`
|
||||||
|
width: 16px;
|
||||||
|
height: 5px;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: ${props => props.theme.palette.grey[50]};
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: flex-grow 0.2s ease-in-out;
|
||||||
|
border-radius: 2px;
|
||||||
|
&:hover {
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
flex-grow: 2;
|
||||||
|
background: ${props => props.theme.palette.grey[50]};
|
||||||
|
opacity: 0.79;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
flex-grow: 2;
|
||||||
|
background: ${props => props.theme.palette.grey[50]};
|
||||||
|
opacity: 1;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(${props => props.currentImageIndex}) {
|
||||||
|
margin: 0 2px 0 2px;
|
||||||
|
flex-grow: 2;
|
||||||
|
background: ${props => props.theme.palette.grey[50]};
|
||||||
|
opacity: 1;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.h1`
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50px;
|
||||||
|
left: 15px;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SubTitle = styled.p`
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
position: absolute;
|
||||||
|
bottom: 30px;
|
||||||
|
left: 15px;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function openNews(e, inf) {
|
||||||
|
e.preventDefault();
|
||||||
|
shell.openExternal(inf.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ImageList({ currentImageIndex, news }) {
|
||||||
|
const listImages = news.map(inf => (
|
||||||
|
<Slide key={inf.guid} onClick={e => openNews(e, inf)}>
|
||||||
|
<Title>{inf.title}</Title>
|
||||||
|
<SubTitle>{inf.description}</SubTitle>
|
||||||
|
<Gradient />
|
||||||
|
<ImageSlide image={inf.image} />
|
||||||
|
</Slide>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ImageSlider currentImageIndex={-1000 * currentImageIndex}>
|
||||||
|
{listImages}
|
||||||
|
</ImageSlider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectNews(props) {
|
||||||
|
const { news } = props;
|
||||||
|
const { setCurrentImageIndex } = props;
|
||||||
|
const selectElementList = news.map((inf, i) => (
|
||||||
|
<SelectElement
|
||||||
|
key={inf.url}
|
||||||
|
onClick={() => setCurrentImageIndex(i)}
|
||||||
|
currentImageIndex={props.currentImageIndex + 1}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
return <Select>{selectElementList}</Select>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useInterval(callback, delay) {
|
||||||
|
const savedCallback = useRef();
|
||||||
|
|
||||||
|
// Remember the latest function.
|
||||||
|
useEffect(() => {
|
||||||
|
savedCallback.current = callback;
|
||||||
|
}, [callback]);
|
||||||
|
|
||||||
|
// Set up the interval.
|
||||||
|
useEffect(() => {
|
||||||
|
function tick() {
|
||||||
|
savedCallback.current();
|
||||||
|
}
|
||||||
|
if (delay !== null) {
|
||||||
|
const id = setInterval(tick, delay);
|
||||||
|
return () => clearInterval(id);
|
||||||
|
}
|
||||||
|
}, [delay]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function News({ style, news }) {
|
||||||
|
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||||
|
const ContextTheme = useContext(ThemeContext);
|
||||||
|
const showNews = useSelector(state => state.settings.showNews);
|
||||||
|
|
||||||
|
useInterval(
|
||||||
|
() => {
|
||||||
|
if (currentImageIndex < 9) {
|
||||||
|
setCurrentImageIndex(currentImageIndex + 1);
|
||||||
|
} else setCurrentImageIndex(0);
|
||||||
|
},
|
||||||
|
showNews ? 5000 : null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!showNews) return null;
|
||||||
|
return news.length !== 0 ? (
|
||||||
|
<Carousel style={style}>
|
||||||
|
<SelectNews
|
||||||
|
news={news}
|
||||||
|
setCurrentImageIndex={setCurrentImageIndex}
|
||||||
|
currentImageIndex={currentImageIndex}
|
||||||
|
/>
|
||||||
|
<ImageList news={news} currentImageIndex={currentImageIndex} />
|
||||||
|
</Carousel>
|
||||||
|
) : (
|
||||||
|
<ContentLoader
|
||||||
|
speed={2}
|
||||||
|
width={1000}
|
||||||
|
height={180}
|
||||||
|
viewBox="0 0 1000 180"
|
||||||
|
foregroundColor={ContextTheme.palette.grey[900]}
|
||||||
|
backgroundColor={ContextTheme.palette.grey[800]}
|
||||||
|
title={false}
|
||||||
|
>
|
||||||
|
{/* <rect x="0" y="0" rx="0" ry="0" width="1000" height="1080" /> */}
|
||||||
|
<rect width="20" height="180" />
|
||||||
|
<rect x="980" width="20" height="180" />
|
||||||
|
<rect
|
||||||
|
x="490"
|
||||||
|
y="-490"
|
||||||
|
transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 510 -490)"
|
||||||
|
width="20"
|
||||||
|
height="1000"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="490"
|
||||||
|
y="-330"
|
||||||
|
transform="matrix(-1.836970e-16 1 -1 -1.836970e-16 670 -330)"
|
||||||
|
width="20"
|
||||||
|
height="1000"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<rect x="40.5" y="100" width="304" height="14.4" />
|
||||||
|
<rect x="40.5" y="125.6" width="304" height="14.4" />
|
||||||
|
</ContentLoader>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default News;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Options = styled.div`
|
||||||
|
position: relative;
|
||||||
|
padding: 1px 18px 17px;
|
||||||
|
margin: 0px -20px 20px;
|
||||||
|
border-bottom: 2px solid rgb(238, 238, 238);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Overview = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Options>
|
||||||
|
<div>Bold</div>
|
||||||
|
</Options>
|
||||||
|
<textarea placeholder="Type your notes here!!" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Overview;
|
||||||
@@ -0,0 +1,437 @@
|
|||||||
|
import React, { useEffect, useState, memo } from 'react';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import {
|
||||||
|
faWindowMinimize,
|
||||||
|
faWindowMaximize,
|
||||||
|
faWindowRestore,
|
||||||
|
faTimes,
|
||||||
|
faTerminal,
|
||||||
|
faCog,
|
||||||
|
faDownload
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { openModal } from '../../../common/reducers/modals/actions';
|
||||||
|
import {
|
||||||
|
checkForPortableUpdates,
|
||||||
|
updateUpdateAvailable,
|
||||||
|
isNewVersionAvailable
|
||||||
|
} from '../../../common/reducers/actions';
|
||||||
|
import BisectHosting from '../../../ui/BisectHosting';
|
||||||
|
import Logo from '../../../ui/Logo';
|
||||||
|
import ga from '../../../common/utils/analytics';
|
||||||
|
|
||||||
|
const isOsx = process.platform === 'darwin';
|
||||||
|
const isLinux = process.platform === 'linux';
|
||||||
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
|
const DevtoolButton = () => {
|
||||||
|
const openDevTools = () => {
|
||||||
|
ipcRenderer.invoke('open-devtools');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TerminalButton
|
||||||
|
css={`
|
||||||
|
margin: 0 10px;
|
||||||
|
`}
|
||||||
|
onClick={openDevTools}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTerminal} />
|
||||||
|
</TerminalButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsButton = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const modals = useSelector(state => state.modals);
|
||||||
|
|
||||||
|
const areSettingsOpen = modals.find(
|
||||||
|
v => v.modalType === 'Settings' && !v.unmounting
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TerminalButton
|
||||||
|
areSettingsOpen={areSettingsOpen}
|
||||||
|
css={`
|
||||||
|
margin: 0 20px 0 10px;
|
||||||
|
${props =>
|
||||||
|
props.areSettingsOpen
|
||||||
|
? `background: ${props.theme.palette.grey[700]};`
|
||||||
|
: null}
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(openModal('Settings'));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faCog} />
|
||||||
|
</TerminalButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateButton = ({ isAppImage }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TerminalButton
|
||||||
|
onClick={() => {
|
||||||
|
if (isAppImage || isWindows) {
|
||||||
|
ipcRenderer.invoke('installUpdateAndQuitOrRestart');
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('AutoUpdatesNotAvailable'));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
css={`
|
||||||
|
color: ${props => props.theme.palette.colors.green};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faDownload} />
|
||||||
|
</TerminalButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SystemNavbar = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
|
const isUpdateAvailable = useSelector(state => state.updateAvailable);
|
||||||
|
const location = useSelector(state => state.router.location.pathname);
|
||||||
|
const [isAppImage, setIsAppImage] = useState(false);
|
||||||
|
|
||||||
|
const checkForUpdates = async () => {
|
||||||
|
const isAppImageVar = await ipcRenderer.invoke('isAppImage');
|
||||||
|
setIsAppImage(isAppImageVar);
|
||||||
|
if (
|
||||||
|
process.env.REACT_APP_RELEASE_TYPE === 'setup' &&
|
||||||
|
(isAppImageVar || process.platform === 'win32')
|
||||||
|
) {
|
||||||
|
ipcRenderer.invoke('checkForUpdates');
|
||||||
|
ipcRenderer.on('updateAvailable', () => {
|
||||||
|
dispatch(updateUpdateAvailable(true));
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
process.platform === 'win32' &&
|
||||||
|
process.env.REACT_APP_RELEASE_TYPE !== 'setup'
|
||||||
|
) {
|
||||||
|
dispatch(checkForPortableUpdates())
|
||||||
|
.then(v => dispatch(updateUpdateAvailable(Boolean(v))))
|
||||||
|
.catch(console.error);
|
||||||
|
} else {
|
||||||
|
isNewVersionAvailable()
|
||||||
|
.then(v => dispatch(updateUpdateAvailable(Boolean(v))))
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('getIsWindowMaximized')
|
||||||
|
.then(setIsMaximized)
|
||||||
|
.catch(console.error);
|
||||||
|
ipcRenderer.on('window-maximized', () => {
|
||||||
|
setIsMaximized(true);
|
||||||
|
});
|
||||||
|
ipcRenderer.on('window-minimized', () => {
|
||||||
|
setIsMaximized(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (process.env.NODE_ENV === 'development') return;
|
||||||
|
setTimeout(() => {
|
||||||
|
checkForUpdates();
|
||||||
|
setInterval(() => {
|
||||||
|
checkForUpdates();
|
||||||
|
}, 600000);
|
||||||
|
}, 1500);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const quitApp = () => {
|
||||||
|
if (isUpdateAvailable && (isAppImage || !isLinux)) {
|
||||||
|
ipcRenderer.invoke('installUpdateAndQuitOrRestart', true);
|
||||||
|
} else {
|
||||||
|
ipcRenderer.invoke('quit-app');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLocation = loc => {
|
||||||
|
if (loc === location) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MainContainer
|
||||||
|
onDoubleClick={() => {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
ipcRenderer.invoke('min-max-window');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!isOsx && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
cursor: auto !important;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
margin-left: 10px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="https://gdevs.io/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
css={`
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Logo size={35} pointerCursor />
|
||||||
|
</a>
|
||||||
|
<DevtoolButton />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
white-space: nowrap;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Partnered with
|
||||||
|
</div>
|
||||||
|
<BisectHosting
|
||||||
|
showPointerCursor
|
||||||
|
onClick={() => {
|
||||||
|
ga.sendCustomEvent('BHAdViewNavbar');
|
||||||
|
dispatch(openModal('BisectHosting'));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* <PulsatingCircle /> */}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Container os={isOsx}>
|
||||||
|
{!isOsx ? (
|
||||||
|
<>
|
||||||
|
{isUpdateAvailable && <UpdateButton isAppImage={isAppImage} />}
|
||||||
|
{!isLocation('/') && !isLocation('/onboarding') && (
|
||||||
|
<SettingsButton />
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
onClick={() => ipcRenderer.invoke('minimize-window')}
|
||||||
|
css={`
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faWindowMinimize} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => ipcRenderer.invoke('min-max-window')}
|
||||||
|
css={`
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={isMaximized ? faWindowRestore : faWindowMaximize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 18px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
onClick={quitApp}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTimes} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 18px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
onClick={quitApp}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faTimes} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => ipcRenderer.invoke('min-max-window')}
|
||||||
|
css={`
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={isMaximized ? faWindowRestore : faWindowMaximize}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
onClick={() => ipcRenderer.invoke('minimize-window')}
|
||||||
|
css={`
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faWindowMinimize} />
|
||||||
|
</div>
|
||||||
|
{!isLocation('/') && !isLocation('/onboarding') && (
|
||||||
|
<SettingsButton />
|
||||||
|
)}
|
||||||
|
{isUpdateAvailable && <UpdateButton isAppImage={isAppImage} />}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
{isOsx && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Partnered with
|
||||||
|
<BisectHosting
|
||||||
|
showPointerCursor
|
||||||
|
onClick={() => dispatch(openModal('BisectHosting'))}
|
||||||
|
/>
|
||||||
|
{/* <PulsatingCircle /> */}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<DevtoolButton />
|
||||||
|
<a
|
||||||
|
href="https://gdevs.io/"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
css={`
|
||||||
|
margin-top: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<Logo size={35} pointerCursor />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MainContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(SystemNavbar);
|
||||||
|
|
||||||
|
const MainContainer = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
height: ${({ theme }) => theme.sizes.height.systemNavbar}px;
|
||||||
|
background: ${({ theme }) => theme.palette.grey[900]};
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100000;
|
||||||
|
& > * {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.1s ease-in-out;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
& > * {
|
||||||
|
width: ${({ theme }) => theme.sizes.height.systemNavbar}px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.palette.grey[700]};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme }) => theme.palette.grey[600]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${props => (props.os ? '& > *:first-child' : '& > *:last-child')} {
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.palette.colors.red};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TerminalButton = styled.div`
|
||||||
|
transition: background 0.1s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
width: ${({ theme }) => theme.sizes.height.systemNavbar}px;
|
||||||
|
height: 100%;
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.palette.grey[700]};
|
||||||
|
}
|
||||||
|
&:active {
|
||||||
|
background: ${({ theme }) => theme.palette.grey[600]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// const opacityPulse = keyframes`
|
||||||
|
// 0% {
|
||||||
|
// -webkit-box-shadow: 0 0 0 0 rgba(39, 174, 96, 0.4);
|
||||||
|
// }
|
||||||
|
// 70% {
|
||||||
|
// -webkit-box-shadow: 0 0 0 10px rgba(39, 174, 96, 0);
|
||||||
|
// }
|
||||||
|
// 100% {
|
||||||
|
// -webkit-box-shadow: 0 0 0 0 rgba(39, 174, 96, 0);
|
||||||
|
// }
|
||||||
|
// `;
|
||||||
|
// const PulsingCircleInner = styled.div`
|
||||||
|
// animation: ${opacityPulse} 1s ease-out;
|
||||||
|
// animation-delay: ${props => props.delay};
|
||||||
|
// animation-iteration-count: infinite;
|
||||||
|
// background: ${props => props.theme.palette.colors.green};
|
||||||
|
// opacity: ${props => props.opacity || 1};
|
||||||
|
// border-radius: 50%;
|
||||||
|
// height: ${props => props.height};
|
||||||
|
// width: ${props => props.width};
|
||||||
|
// position: absolute;
|
||||||
|
// display: inline-block;
|
||||||
|
// text-align: center;
|
||||||
|
// `;
|
||||||
|
// const PulsingCircleContainer = styled.div`
|
||||||
|
// display: flex;
|
||||||
|
// align-items: center;
|
||||||
|
// justify-content: center;
|
||||||
|
// position: relative;
|
||||||
|
// margin-left: 1rem;
|
||||||
|
// margin-right: 1.5rem;
|
||||||
|
// top: -1px;
|
||||||
|
// `;
|
||||||
|
|
||||||
|
// function PulsatingCircle() {
|
||||||
|
// return (
|
||||||
|
// <PulsingCircleContainer>
|
||||||
|
// <PulsingCircleInner delay="0s" height="17px" width="17px" opacity={0.1} />
|
||||||
|
// <PulsingCircleInner
|
||||||
|
// delay="0.5s"
|
||||||
|
// height="12.75"
|
||||||
|
// width="12.75"
|
||||||
|
// opacity={0.15}
|
||||||
|
// />
|
||||||
|
// <PulsingCircleInner delay="1s" height="8.5px" width="8.5px" />
|
||||||
|
// </PulsingCircleContainer>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// This class is used to create a queue of operations when working with instances configs, to avoid data corruption.
|
||||||
|
// Each instance will have its own queue. This can also be used in more generic situations where a queue structure is needed
|
||||||
|
class PromiseQueue {
|
||||||
|
constructor() {
|
||||||
|
this.queue = [];
|
||||||
|
this.isPending = false;
|
||||||
|
this.listeners = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
add(promise) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.queue.push({
|
||||||
|
promise,
|
||||||
|
resolve,
|
||||||
|
reject
|
||||||
|
});
|
||||||
|
this.execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
on(eventName, handler) {
|
||||||
|
switch (eventName) {
|
||||||
|
case 'executed':
|
||||||
|
this.listeners.executed = () => handler(this.queue.length + 1);
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
this.listeners.start = () => handler(this.queue.length + 1);
|
||||||
|
break;
|
||||||
|
case 'end':
|
||||||
|
this.listeners.end = () => handler();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute() {
|
||||||
|
const startHandler = this.listeners.start;
|
||||||
|
if (this.isPending) return false;
|
||||||
|
if (startHandler) {
|
||||||
|
setTimeout(startHandler, 0);
|
||||||
|
}
|
||||||
|
while (this.queue[0]) {
|
||||||
|
const item = this.queue.shift();
|
||||||
|
this.isPending = true;
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const value = await item.promise();
|
||||||
|
const executedHandler = this.listeners.executed;
|
||||||
|
if (executedHandler) {
|
||||||
|
setTimeout(executedHandler, 0);
|
||||||
|
}
|
||||||
|
item.resolve(value);
|
||||||
|
} catch (e) {
|
||||||
|
item.reject(e);
|
||||||
|
}
|
||||||
|
this.isPending = false;
|
||||||
|
}
|
||||||
|
const endHandler = this.listeners.end;
|
||||||
|
if (endHandler) {
|
||||||
|
setTimeout(endHandler, 0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PromiseQueue;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
const computeFileHash = (filePath, algorithm = 'sha1') =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const hash = crypto.createHash(algorithm);
|
||||||
|
fs.createReadStream(filePath)
|
||||||
|
.on('data', data => hash.update(data))
|
||||||
|
.on('end', () => resolve(hash.digest('hex')))
|
||||||
|
.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default computeFileHash;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { platform, homedir } from 'os';
|
||||||
|
|
||||||
|
export const WINDOWS = 'win32';
|
||||||
|
export const LINUX = 'linux';
|
||||||
|
export const DARWIN = 'darwin';
|
||||||
|
export const DESKTOP_PATH = path.join(homedir(), 'Desktop');
|
||||||
|
export const CLASSPATH_DIVIDER_CHAR = platform() === WINDOWS ? ';' : ':';
|
||||||
|
export const DEFAULT_JAVA_ARGS = `${
|
||||||
|
platform() === WINDOWS
|
||||||
|
? '-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump'
|
||||||
|
: ''
|
||||||
|
} -Xms256m`;
|
||||||
|
export const REQUIRED_JAVA_ARGS =
|
||||||
|
'-Dfml.ignorePatchDiscrepancies=true -Dfml.ignoreInvalidMinecraftCertificates=true';
|
||||||
|
export const DEFAULT_MEMORY = 4096;
|
||||||
|
|
||||||
|
export const resolutionPresets = [
|
||||||
|
'854x480',
|
||||||
|
'800x600',
|
||||||
|
'1024x768',
|
||||||
|
'1280x1024',
|
||||||
|
'1366x768',
|
||||||
|
'1600x900',
|
||||||
|
'1920x1080',
|
||||||
|
'2560x1440',
|
||||||
|
'3440x1440',
|
||||||
|
'3440x1500'
|
||||||
|
];
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
import makeDir from 'make-dir';
|
||||||
|
import fss from 'fs';
|
||||||
|
import axios from 'axios';
|
||||||
|
import pMap from 'p-map';
|
||||||
|
import path from 'path';
|
||||||
|
import adapter from 'axios/lib/adapters/http';
|
||||||
|
import computeFileHash from './computeFileHash';
|
||||||
|
|
||||||
|
const fs = fss.promises;
|
||||||
|
|
||||||
|
function getUri(url) {
|
||||||
|
return new URL(url).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const downloadInstanceFiles = async (
|
||||||
|
arr,
|
||||||
|
updatePercentage,
|
||||||
|
threads = 4
|
||||||
|
) => {
|
||||||
|
let downloaded = 0;
|
||||||
|
await pMap(
|
||||||
|
arr,
|
||||||
|
async item => {
|
||||||
|
let counter = 0;
|
||||||
|
let res = false;
|
||||||
|
if (!item.path || !item.url) {
|
||||||
|
console.warn('Skipping', item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
counter += 1;
|
||||||
|
if (counter !== 1) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
res = await downloadFileInstance(
|
||||||
|
item.path,
|
||||||
|
item.url,
|
||||||
|
item.sha1,
|
||||||
|
item.legacyPath
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
} while (!res && counter < 3);
|
||||||
|
downloaded += 1;
|
||||||
|
if (
|
||||||
|
updatePercentage &&
|
||||||
|
(downloaded % 5 === 0 || downloaded === arr.length)
|
||||||
|
) {
|
||||||
|
updatePercentage(downloaded);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ concurrency: threads }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFileInstance = async (fileName, url, sha1, legacyPath) => {
|
||||||
|
let encodedUrl;
|
||||||
|
try {
|
||||||
|
const filePath = path.dirname(fileName);
|
||||||
|
try {
|
||||||
|
await fs.access(fileName);
|
||||||
|
if (legacyPath) await fs.access(legacyPath);
|
||||||
|
const checksum = await computeFileHash(fileName);
|
||||||
|
const legacyChecksum = legacyPath && (await computeFileHash(legacyPath));
|
||||||
|
if (checksum === sha1 && (!legacyPath || legacyChecksum === sha1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
await makeDir(filePath);
|
||||||
|
if (legacyPath) await makeDir(path.dirname(legacyPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
encodedUrl = getUri(url);
|
||||||
|
|
||||||
|
const { data } = await axios.get(encodedUrl, {
|
||||||
|
responseType: 'stream',
|
||||||
|
responseEncoding: null,
|
||||||
|
adapter,
|
||||||
|
timeout: 60000 * 20
|
||||||
|
});
|
||||||
|
|
||||||
|
const wStream = fss.createWriteStream(fileName, {
|
||||||
|
encoding: null
|
||||||
|
});
|
||||||
|
|
||||||
|
data.pipe(wStream);
|
||||||
|
let wStreamLegacy;
|
||||||
|
if (legacyPath) {
|
||||||
|
wStreamLegacy = fss.createWriteStream(legacyPath, {
|
||||||
|
encoding: null
|
||||||
|
});
|
||||||
|
data.pipe(wStreamLegacy);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
data.on('error', err => {
|
||||||
|
console.error(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
data.on('end', () => {
|
||||||
|
wStream.end();
|
||||||
|
wStream.close();
|
||||||
|
if (legacyPath) {
|
||||||
|
wStreamLegacy.end();
|
||||||
|
wStreamLegacy.close();
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(
|
||||||
|
`Error while downloading <${url} | ${encodedUrl}> to <${fileName}> --> ${e.message}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const downloadFile = async (fileName, url, onProgress) => {
|
||||||
|
await makeDir(path.dirname(fileName));
|
||||||
|
|
||||||
|
const encodedUrl = getUri(url);
|
||||||
|
|
||||||
|
const { data, headers } = await axios.get(encodedUrl, {
|
||||||
|
responseType: 'stream',
|
||||||
|
responseEncoding: null,
|
||||||
|
adapter,
|
||||||
|
timeout: 60000 * 20
|
||||||
|
});
|
||||||
|
|
||||||
|
const out = fss.createWriteStream(fileName, { encoding: null });
|
||||||
|
data.pipe(out);
|
||||||
|
|
||||||
|
// Save variable to know progress
|
||||||
|
let receivedBytes = 0;
|
||||||
|
const totalBytes = parseInt(headers['content-length'], 10);
|
||||||
|
|
||||||
|
data.on('data', chunk => {
|
||||||
|
// Update the received bytes
|
||||||
|
receivedBytes += chunk.length;
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(parseInt(((receivedBytes * 100) / totalBytes).toFixed(1), 10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
data.on('end', () => {
|
||||||
|
out.end();
|
||||||
|
out.close();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
data.on('error', () => {
|
||||||
|
reject();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
const fmlLibsMapping = {};
|
||||||
|
// 1.3.*
|
||||||
|
const libs13 = [
|
||||||
|
['argo-2.25.jar', 'bb672829fde76cb163004752b86b0484bd0a7f4b', false],
|
||||||
|
['guava-12.0.1.jar', 'b8e78b9af7bf45900e14c6f958486b6ca682195f', false],
|
||||||
|
['asm-all-4.0.jar', '98308890597acb64047f7e896638e0d98753ae82', false]
|
||||||
|
];
|
||||||
|
|
||||||
|
fmlLibsMapping['1.3.2'] = libs13;
|
||||||
|
|
||||||
|
// 1.4.*
|
||||||
|
const libs14 = [
|
||||||
|
['argo-2.25.jar', 'bb672829fde76cb163004752b86b0484bd0a7f4b', false],
|
||||||
|
['guava-12.0.1.jar', 'b8e78b9af7bf45900e14c6f958486b6ca682195f', false],
|
||||||
|
['asm-all-4.0.jar', '98308890597acb64047f7e896638e0d98753ae82', false],
|
||||||
|
['bcprov-jdk15on-147.jar', 'b6f5d9926b0afbde9f4dbe3db88c5247be7794bb', false]
|
||||||
|
];
|
||||||
|
|
||||||
|
fmlLibsMapping['1.4'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.1'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.2'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.3'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.4'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.5'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.6'] = libs14;
|
||||||
|
fmlLibsMapping['1.4.7'] = libs14;
|
||||||
|
|
||||||
|
// 1.5
|
||||||
|
fmlLibsMapping['1.5'] = [
|
||||||
|
['argo-small-3.2.jar', '58912ea2858d168c50781f956fa5b59f0f7c6b51', false],
|
||||||
|
['guava-14.0-rc3.jar', '931ae21fa8014c3ce686aaa621eae565fefb1a6a', false],
|
||||||
|
['asm-all-4.1.jar', '054986e962b88d8660ae4566475658469595ef58', false],
|
||||||
|
['bcprov-jdk15on-148.jar', '960dea7c9181ba0b17e8bab0c06a43f0a5f04e65', true],
|
||||||
|
[
|
||||||
|
'deobfuscation_data_1.5.zip',
|
||||||
|
'5f7c142d53776f16304c0bbe10542014abad6af8',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
['scala-library.jar', '458d046151ad179c85429ed7420ffb1eaf6ddf85', true]
|
||||||
|
];
|
||||||
|
|
||||||
|
// 1.5.1
|
||||||
|
fmlLibsMapping['1.5.1'] = [
|
||||||
|
['argo-small-3.2.jar', '58912ea2858d168c50781f956fa5b59f0f7c6b51', false],
|
||||||
|
['guava-14.0-rc3.jar', '931ae21fa8014c3ce686aaa621eae565fefb1a6a', false],
|
||||||
|
['asm-all-4.1.jar', '054986e962b88d8660ae4566475658469595ef58', false],
|
||||||
|
['bcprov-jdk15on-148.jar', '960dea7c9181ba0b17e8bab0c06a43f0a5f04e65', true],
|
||||||
|
[
|
||||||
|
'deobfuscation_data_1.5.1.zip',
|
||||||
|
'22e221a0d89516c1f721d6cab056a7e37471d0a6',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
['scala-library.jar', '458d046151ad179c85429ed7420ffb1eaf6ddf85', true]
|
||||||
|
];
|
||||||
|
|
||||||
|
// 1.5.2
|
||||||
|
fmlLibsMapping['1.5.2'] = [
|
||||||
|
['argo-small-3.2.jar', '58912ea2858d168c50781f956fa5b59f0f7c6b51', false],
|
||||||
|
['guava-14.0-rc3.jar', '931ae21fa8014c3ce686aaa621eae565fefb1a6a', false],
|
||||||
|
['asm-all-4.1.jar', '054986e962b88d8660ae4566475658469595ef58', false],
|
||||||
|
['bcprov-jdk15on-148.jar', '960dea7c9181ba0b17e8bab0c06a43f0a5f04e65', true],
|
||||||
|
[
|
||||||
|
'deobfuscation_data_1.5.2.zip',
|
||||||
|
'446e55cd986582c70fcf12cb27bc00114c5adfd9',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
['scala-library.jar', '458d046151ad179c85429ed7420ffb1eaf6ddf85', true]
|
||||||
|
];
|
||||||
|
|
||||||
|
export default fmlLibsMapping;
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import fse from 'fs-extra';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import pMap from 'p-map';
|
||||||
|
import { getDirectories } from '.';
|
||||||
|
import { CURSEFORGE } from '../../../common/utils/constants';
|
||||||
|
|
||||||
|
const getInstances = async instancesPath => {
|
||||||
|
const mapFolderToInstance = async instance => {
|
||||||
|
try {
|
||||||
|
const configPath = path.join(
|
||||||
|
path.join(instancesPath, instance, 'config.json')
|
||||||
|
);
|
||||||
|
const rawConfig = await fs.readFile(configPath);
|
||||||
|
|
||||||
|
// Remove temp config if present
|
||||||
|
try {
|
||||||
|
const tempConfigPath = path.join(
|
||||||
|
path.join(instancesPath, instance, 'config_new_temp.json')
|
||||||
|
);
|
||||||
|
await fs.unlink(tempConfigPath);
|
||||||
|
} catch {
|
||||||
|
// Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore config in case of crash
|
||||||
|
if (rawConfig.every(v => v === 0)) {
|
||||||
|
const backupConfigPath = path.join(
|
||||||
|
path.join(instancesPath, instance, 'config.bak.json')
|
||||||
|
);
|
||||||
|
const backupConfig = await fs.readFile(backupConfigPath);
|
||||||
|
JSON.parse(backupConfig);
|
||||||
|
await fs.rename(backupConfigPath, configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRawConfig = await fs.readFile(configPath);
|
||||||
|
const config = JSON.parse(newRawConfig);
|
||||||
|
|
||||||
|
// if the launcher has the modloader as an array, convert it to object
|
||||||
|
if (Array.isArray(config.modloader)) {
|
||||||
|
// source is the source where the modpack comes from example: curseforge
|
||||||
|
// loaderType is the modloader example: forge
|
||||||
|
const [
|
||||||
|
loaderType,
|
||||||
|
mcVersion,
|
||||||
|
loaderVersion,
|
||||||
|
projectID,
|
||||||
|
fileID,
|
||||||
|
source
|
||||||
|
] = config.modloader;
|
||||||
|
|
||||||
|
const patchedConfig = {
|
||||||
|
...config,
|
||||||
|
loader: {
|
||||||
|
loaderType,
|
||||||
|
mcVersion,
|
||||||
|
...(loaderVersion && { loaderVersion }),
|
||||||
|
...(fileID && { fileID }),
|
||||||
|
...(projectID && { projectID }),
|
||||||
|
...(!source && fileID && projectID && { source: CURSEFORGE })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
delete patchedConfig.modloader;
|
||||||
|
|
||||||
|
await fse.writeFile(configPath, JSON.stringify(patchedConfig));
|
||||||
|
|
||||||
|
return { ...patchedConfig, name: instance };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.loader?.fileId ||
|
||||||
|
config.loader?.addonId ||
|
||||||
|
config.loader?.addonID
|
||||||
|
) {
|
||||||
|
const { fileId, addonId, addonID } = config.loader;
|
||||||
|
|
||||||
|
const patchedConfig = {
|
||||||
|
...config,
|
||||||
|
loader: {
|
||||||
|
...config.loader,
|
||||||
|
...(fileId && { fileID: fileId }),
|
||||||
|
...(addonId && { projectID: addonId }),
|
||||||
|
...(addonID && { projectID: addonID })
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
delete patchedConfig.loader.fileId;
|
||||||
|
delete patchedConfig.loader.addonId;
|
||||||
|
|
||||||
|
await fse.writeFile(configPath, JSON.stringify(patchedConfig));
|
||||||
|
|
||||||
|
return { ...patchedConfig, name: instance };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
name: instance
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const folders = await getDirectories(instancesPath);
|
||||||
|
const instances = await pMap(
|
||||||
|
folders.filter(folder => folder !== '.DS_Store'),
|
||||||
|
mapFolderToInstance,
|
||||||
|
{
|
||||||
|
concurrency: 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const hashMap = {};
|
||||||
|
// eslint-disable-next-line
|
||||||
|
for (const instance of instances) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (!instance) continue;
|
||||||
|
hashMap[instance.name] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getInstances;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { push } from 'connected-react-router';
|
||||||
|
|
||||||
|
const middleware = store => next => action => {
|
||||||
|
const currState = store.getState();
|
||||||
|
const result = next(action);
|
||||||
|
const nextState = store.getState();
|
||||||
|
const { dispatch } = store;
|
||||||
|
|
||||||
|
const currentAccountIdChanged =
|
||||||
|
currState.app.currentAccountId !== nextState.app.currentAccountId;
|
||||||
|
|
||||||
|
if (currentAccountIdChanged && !nextState.app.currentAccountId) {
|
||||||
|
dispatch(push('/'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currState.settings.potatoPcMode !== nextState.settings.potatoPcMode) {
|
||||||
|
if (nextState.settings.potatoPcMode) {
|
||||||
|
document.getElementById('root').classList.add('disable-animations');
|
||||||
|
} else {
|
||||||
|
document.getElementById('root').classList.remove('disable-animations');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default middleware;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// import watch from "node-watch";
|
||||||
|
import makeDir from 'make-dir';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import { notification } from 'antd';
|
||||||
|
import * as ActionTypes from '../../../common/reducers/actionTypes';
|
||||||
|
import getInstances from './getInstances';
|
||||||
|
import modsFingerprintsScan from './modsFingerprintsScan';
|
||||||
|
import { startListener } from '../../../common/reducers/actions';
|
||||||
|
import { _getInstancesPath } from '../../../common/utils/selectors';
|
||||||
|
|
||||||
|
const middleware = store => next => action => {
|
||||||
|
const currState = store.getState();
|
||||||
|
const result = next(action);
|
||||||
|
const nextState = store.getState();
|
||||||
|
const { dispatch } = store;
|
||||||
|
if (!nextState.userData) return result;
|
||||||
|
const instancesPath = _getInstancesPath(nextState);
|
||||||
|
|
||||||
|
const userDataChanged = currState.userData !== nextState.userData;
|
||||||
|
|
||||||
|
// If not initialized yet, start listener and do a first-time read
|
||||||
|
if (!nextState.instances.started || userDataChanged) {
|
||||||
|
const startInstancesListener = async () => {
|
||||||
|
await ipcRenderer.invoke('stop-listener');
|
||||||
|
await makeDir(instancesPath);
|
||||||
|
const instances = await getInstances(instancesPath);
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.UPDATE_INSTANCES,
|
||||||
|
instances
|
||||||
|
});
|
||||||
|
const instances1 = await modsFingerprintsScan(instancesPath);
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.UPDATE_INSTANCES,
|
||||||
|
instances: instances1
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await makeDir(instancesPath);
|
||||||
|
await dispatch(startListener());
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
// eslint-disable-next-line
|
||||||
|
notification.open({
|
||||||
|
key: 'nsfwNotWorking',
|
||||||
|
message: 'NSFW Error',
|
||||||
|
description: 'Node Sentinel File Watcher could not be initialized',
|
||||||
|
top: 47,
|
||||||
|
duration: 10
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.UPDATE_INSTANCES_STARTED,
|
||||||
|
started: true
|
||||||
|
});
|
||||||
|
startInstancesListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default middleware;
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import fse from 'fs-extra';
|
||||||
|
import pMap from 'p-map';
|
||||||
|
import { getDirectories, normalizeModData, isMod } from '.';
|
||||||
|
import { getFileMurmurHash2 } from '../../../common/utils';
|
||||||
|
import { getAddonsByFingerprint, getAddon } from '../../../common/api';
|
||||||
|
|
||||||
|
const modsFingerprintsScan = async instancesPath => {
|
||||||
|
const mapFolderToInstance = async instance => {
|
||||||
|
try {
|
||||||
|
const configPath = path.join(
|
||||||
|
path.join(instancesPath, instance, 'config.json')
|
||||||
|
);
|
||||||
|
const config = await fse.readJSON(configPath);
|
||||||
|
|
||||||
|
if (!config.loader) {
|
||||||
|
throw new Error(`Config for ${instance} could not be parsed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modsFolder = path.join(instancesPath, instance, 'mods');
|
||||||
|
const modsFolderExists = await fse.pathExists(modsFolder);
|
||||||
|
|
||||||
|
if (!modsFolderExists) return { ...config, name: instance };
|
||||||
|
|
||||||
|
// Check if config.mods has a different number of mods than the actual number of mods
|
||||||
|
|
||||||
|
// Count the actual mods inside the folder
|
||||||
|
const files = await fs.readdir(modsFolder);
|
||||||
|
|
||||||
|
const fileNamesToRemove = [];
|
||||||
|
const missingMods = {};
|
||||||
|
/* eslint-disable */
|
||||||
|
// Check for new mods in local storage that are not present in config
|
||||||
|
for (const file of files) {
|
||||||
|
try {
|
||||||
|
const completeFilePath = path.join(modsFolder, file);
|
||||||
|
const stat = await fs.lstat(completeFilePath);
|
||||||
|
if (stat.isFile() && isMod(completeFilePath, instancesPath)) {
|
||||||
|
// Check if file is in config
|
||||||
|
if (!(config?.mods || []).find(mod => mod.fileName === file)) {
|
||||||
|
const murmurHash = await getFileMurmurHash2(completeFilePath);
|
||||||
|
console.log(
|
||||||
|
'[MODS SCANNER] Local mod not found in config',
|
||||||
|
file,
|
||||||
|
murmurHash
|
||||||
|
);
|
||||||
|
missingMods[file] = murmurHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for old mods in config that are not present on local storage
|
||||||
|
for (const configMod of config?.mods || []) {
|
||||||
|
if (!files.includes(configMod.fileName)) {
|
||||||
|
fileNamesToRemove.push(configMod.fileName);
|
||||||
|
console.log(
|
||||||
|
`[MODS SCANNER] Removing ${configMod.fileName} from config`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
|
let newMods = config?.mods || [];
|
||||||
|
|
||||||
|
if (Object.values(missingMods).length !== 0) {
|
||||||
|
let success = false;
|
||||||
|
let tries = 10;
|
||||||
|
|
||||||
|
while (!success || tries > 10) {
|
||||||
|
try {
|
||||||
|
const data = await getAddonsByFingerprint(
|
||||||
|
Object.values(missingMods)
|
||||||
|
);
|
||||||
|
|
||||||
|
const matches = await Promise.all(
|
||||||
|
Object.entries(missingMods).map(async ([fileName, hash]) => {
|
||||||
|
const exactMatch = (data.exactMatches || []).find(
|
||||||
|
v => v.file.fileFingerprint === hash
|
||||||
|
);
|
||||||
|
if (exactMatch?.file) {
|
||||||
|
let addonData = null;
|
||||||
|
try {
|
||||||
|
addonData = await getAddon(exactMatch.file.modId);
|
||||||
|
return {
|
||||||
|
...normalizeModData(
|
||||||
|
exactMatch.file,
|
||||||
|
exactMatch.file.modId,
|
||||||
|
addonData.name
|
||||||
|
),
|
||||||
|
fileName
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
fileName,
|
||||||
|
displayName: fileName,
|
||||||
|
packageFingerprint: hash
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fileName,
|
||||||
|
displayName: fileName,
|
||||||
|
packageFingerprint: hash
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
newMods = [...newMods, ...matches];
|
||||||
|
success = true;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
tries += 1;
|
||||||
|
await new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const filterMods = newMods
|
||||||
|
.filter(_ => _)
|
||||||
|
.filter(v => !fileNamesToRemove.includes(v.fileName));
|
||||||
|
|
||||||
|
const newConfig = {
|
||||||
|
...config,
|
||||||
|
mods: filterMods
|
||||||
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(config) !== JSON.stringify(newConfig)) {
|
||||||
|
await fse.outputJson(configPath, newConfig);
|
||||||
|
return { ...newConfig, name: instance };
|
||||||
|
}
|
||||||
|
return { ...config, name: instance };
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const folders = await getDirectories(instancesPath);
|
||||||
|
const instances = await pMap(
|
||||||
|
folders.filter(folder => folder !== '.DS_Store'),
|
||||||
|
mapFolderToInstance,
|
||||||
|
{
|
||||||
|
concurrency: 5
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const hashMap = {};
|
||||||
|
// eslint-disable-next-line
|
||||||
|
for (const instance of instances) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (!instance) continue;
|
||||||
|
hashMap[instance.name] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashMap;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default modsFingerprintsScan;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { lazy } from 'react';
|
||||||
|
import AsyncComponent from '../../../common/components/AsyncComponent';
|
||||||
|
import withScroll from './withScroll';
|
||||||
|
|
||||||
|
const Login = lazy(() => import('../views/Login'));
|
||||||
|
const Home = lazy(() => import('../views/Home'));
|
||||||
|
const Onboarding = lazy(() => import('../views/Onboarding'));
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
exact: true,
|
||||||
|
component: AsyncComponent(Login)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
component: withScroll(AsyncComponent(Home))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/onboarding',
|
||||||
|
component: AsyncComponent(Onboarding)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import analytics from '../../../common/utils/analytics';
|
||||||
|
|
||||||
|
const INTERVAL_DURATION = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
const useTrackIdle = pathname => {
|
||||||
|
const clientToken = useSelector(state => state.app.clientToken);
|
||||||
|
useEffect(() => {
|
||||||
|
let interval;
|
||||||
|
if (clientToken && process.env.NODE_ENV !== 'development') {
|
||||||
|
interval = setInterval(() => {
|
||||||
|
analytics.idle(pathname);
|
||||||
|
}, INTERVAL_DURATION);
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
if (interval) {
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [pathname, clientToken]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTrackIdle;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const withScroll = Component => {
|
||||||
|
return props => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
width: 1000px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<Component {...props} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withScroll;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState, useEffect, memo } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import axios from 'axios';
|
||||||
|
// import { promises as fs } from 'fs';
|
||||||
|
// import path from 'path';
|
||||||
|
import Instances from '../components/Instances';
|
||||||
|
import News from '../components/News';
|
||||||
|
import { openModal } from '../../../common/reducers/modals/actions';
|
||||||
|
import {
|
||||||
|
_getCurrentAccount
|
||||||
|
// _getInstances
|
||||||
|
} from '../../../common/utils/selectors';
|
||||||
|
import { extractFace } from '../utils';
|
||||||
|
import { updateLastUpdateVersion } from '../../../common/reducers/actions';
|
||||||
|
|
||||||
|
const AddInstanceIcon = styled(Button)`
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AccountContainer = styled(Button)`
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const account = useSelector(_getCurrentAccount);
|
||||||
|
const news = useSelector(state => state.news);
|
||||||
|
const lastUpdateVersion = useSelector(state => state.app.lastUpdateVersion);
|
||||||
|
// const instances = useSelector(_getInstances);
|
||||||
|
|
||||||
|
const openAddInstanceModal = defaultPage => {
|
||||||
|
dispatch(openModal('AddInstance', { defaultPage }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const openAccountModal = () => {
|
||||||
|
dispatch(openModal('AccountsManager'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const [profileImage, setProfileImage] = useState(null);
|
||||||
|
const [annoucement, setAnnoucement] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const init = async () => {
|
||||||
|
const appVersion = await ipcRenderer.invoke('getAppVersion');
|
||||||
|
if (lastUpdateVersion !== appVersion) {
|
||||||
|
dispatch(updateLastUpdateVersion(appVersion));
|
||||||
|
dispatch(openModal('ChangeLogs'));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
setAnnoucement(data || null);
|
||||||
|
} catch (e) {
|
||||||
|
console.log('No announcement to show');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
extractFace(account.skin).then(setProfileImage).catch(console.error);
|
||||||
|
}, [account]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<News news={news} />
|
||||||
|
{annoucement ? (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 30px;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: ${props => props.theme.palette.colors.yellow};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{annoucement}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<Instances />
|
||||||
|
<AddInstanceIcon type="primary" onClick={() => openAddInstanceModal(0)}>
|
||||||
|
<FontAwesomeIcon icon={faPlus} />
|
||||||
|
</AddInstanceIcon>
|
||||||
|
<AccountContainer type="primary" onClick={openAccountModal}>
|
||||||
|
{profileImage ? (
|
||||||
|
<img
|
||||||
|
src={`data:image/jpeg;base64,${profileImage}`}
|
||||||
|
css={`
|
||||||
|
width: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 15px;
|
||||||
|
margin-right: 10px;
|
||||||
|
`}
|
||||||
|
alt="profile"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
background: ${props => props.theme.palette.grey[100]};
|
||||||
|
margin-right: 10px;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{account && account.selectedProfile.name}
|
||||||
|
</AccountContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Home);
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
import React, { useState, useEffect, memo } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Transition } from 'react-transition-group';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import {
|
||||||
|
faArrowRight,
|
||||||
|
faExternalLinkAlt
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { Input, Button } from 'antd';
|
||||||
|
import { useKey } from 'rooks';
|
||||||
|
import { login, loginOAuth } from '../../../common/reducers/actions';
|
||||||
|
import { load, requesting } from '../../../common/reducers/loading/actions';
|
||||||
|
import features from '../../../common/reducers/loading/features';
|
||||||
|
import backgroundVideo from '../../../common/assets/background.webm';
|
||||||
|
import HorizontalLogo from '../../../ui/HorizontalLogo';
|
||||||
|
import { openModal } from '../../../common/reducers/modals/actions';
|
||||||
|
|
||||||
|
const LoginButton = styled(Button)`
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 22px;
|
||||||
|
background: ${props =>
|
||||||
|
props.active ? props.theme.palette.grey[600] : 'transparent'};
|
||||||
|
border: 0;
|
||||||
|
height: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
background: ${props => props.theme.palette.grey[600]};
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
background: ${props => props.theme.palette.grey[600]};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const MicrosoftLoginButton = styled(LoginButton)`
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const LeftSide = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 300px;
|
||||||
|
padding: 40px;
|
||||||
|
height: 100%;
|
||||||
|
transition: 0.3s ease-in-out;
|
||||||
|
transform: translateX(
|
||||||
|
${({ transitionState }) =>
|
||||||
|
transitionState === 'entering' || transitionState === 'entered'
|
||||||
|
? -300
|
||||||
|
: 0}px
|
||||||
|
);
|
||||||
|
background: ${props => props.theme.palette.secondary.main};
|
||||||
|
& div {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-top: 1em;
|
||||||
|
color: ${props => props.theme.palette.text.third};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Form = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0 !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Background = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
video {
|
||||||
|
transition: 0.3s ease-in-out;
|
||||||
|
transform: translateX(
|
||||||
|
${({ transitionState }) =>
|
||||||
|
transitionState === 'entering' || transitionState === 'entered'
|
||||||
|
? -300
|
||||||
|
: 0}px
|
||||||
|
);
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
height: 150%;
|
||||||
|
top: -30%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Header = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Footer = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: calc(100% - 80px);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FooterLinks = styled.div`
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin: 0 !important;
|
||||||
|
a {
|
||||||
|
color: ${props => props.theme.palette.text.third};
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: ${props => props.theme.palette.text.secondary};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Loading = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
z-index: -1;
|
||||||
|
justify-content: center;
|
||||||
|
backdrop-filter: blur(8px) brightness(60%);
|
||||||
|
font-size: 40px;
|
||||||
|
transition: 0.3s ease-in-out;
|
||||||
|
opacity: ${({ transitionState }) =>
|
||||||
|
transitionState === 'entering' || transitionState === 'entered' ? 1 : 0};
|
||||||
|
`;
|
||||||
|
const LoginFailMessage = styled.div`
|
||||||
|
color: ${props => props.theme.palette.colors.red};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Login = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [email, setEmail] = useState(null);
|
||||||
|
const [password, setPassword] = useState(null);
|
||||||
|
const [version, setVersion] = useState(null);
|
||||||
|
const [loginFailed, setLoginFailed] = useState(false);
|
||||||
|
const loading = useSelector(
|
||||||
|
state => state.loading.accountAuthentication.isRequesting
|
||||||
|
);
|
||||||
|
|
||||||
|
const authenticate = () => {
|
||||||
|
if (!email || !password) return;
|
||||||
|
dispatch(requesting('accountAuthentication'));
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch(
|
||||||
|
load(features.mcAuthentication, dispatch(login(email, password)))
|
||||||
|
).catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
setLoginFailed(e);
|
||||||
|
setPassword(null);
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const authenticateMicrosoft = () => {
|
||||||
|
dispatch(requesting('accountAuthentication'));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
dispatch(load(features.mcAuthentication, dispatch(loginOAuth()))).catch(
|
||||||
|
e => {
|
||||||
|
console.error(e);
|
||||||
|
setLoginFailed(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
useKey(['Enter'], authenticate);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ipcRenderer.invoke('getAppVersion').then(setVersion).catch(console.error);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Transition in={loading} timeout={300}>
|
||||||
|
{transitionState => (
|
||||||
|
<Container>
|
||||||
|
<LeftSide transitionState={transitionState}>
|
||||||
|
<Header>
|
||||||
|
<HorizontalLogo size={200} />
|
||||||
|
</Header>
|
||||||
|
<Form>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="Email"
|
||||||
|
value={email}
|
||||||
|
onChange={({ target: { value } }) => setEmail(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={({ target: { value } }) => setPassword(value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{loginFailed && (
|
||||||
|
<LoginFailMessage>{loginFailed?.message}</LoginFailMessage>
|
||||||
|
)}
|
||||||
|
<LoginButton color="primary" onClick={authenticate}>
|
||||||
|
Sign In
|
||||||
|
<FontAwesomeIcon
|
||||||
|
css={`
|
||||||
|
margin-left: 6px;
|
||||||
|
`}
|
||||||
|
icon={faArrowRight}
|
||||||
|
/>
|
||||||
|
</LoginButton>
|
||||||
|
<MicrosoftLoginButton
|
||||||
|
color="primary"
|
||||||
|
onClick={authenticateMicrosoft}
|
||||||
|
>
|
||||||
|
Sign in with Microsoft
|
||||||
|
<FontAwesomeIcon
|
||||||
|
css={`
|
||||||
|
margin-left: 6px;
|
||||||
|
`}
|
||||||
|
icon={faExternalLinkAlt}
|
||||||
|
/>
|
||||||
|
</MicrosoftLoginButton>
|
||||||
|
</Form>
|
||||||
|
<Footer>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FooterLinks>
|
||||||
|
<div>
|
||||||
|
<a href="https://www.minecraft.net/it-it/password/forgot">
|
||||||
|
FORGOT PASSWORD
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FooterLinks>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
cursor: pointer;
|
||||||
|
`}
|
||||||
|
onClick={() => dispatch(openModal('ChangeLogs'))}
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
css={`
|
||||||
|
font-size: 10px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Sign in with your Mojang Account. By doing so, you accept all
|
||||||
|
our policies and terms stated below.
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: row;
|
||||||
|
span {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(openModal('PolicyModal', { policy: 'privacy' }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(openModal('PolicyModal', { policy: 'tos' }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Terms and Conditions
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
onClick={() =>
|
||||||
|
dispatch(
|
||||||
|
openModal('PolicyModal', { policy: 'acceptableuse' })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Acceptable Use Policy
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Footer>
|
||||||
|
</LeftSide>
|
||||||
|
<Background transitionState={transitionState}>
|
||||||
|
<video autoPlay muted loop>
|
||||||
|
<source src={backgroundVideo} type="video/webm" />
|
||||||
|
</video>
|
||||||
|
</Background>
|
||||||
|
<Loading transitionState={transitionState}>Loading...</Loading>
|
||||||
|
</Container>
|
||||||
|
)}
|
||||||
|
</Transition>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Login);
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
import React, { useRef, useState, memo, useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { push } from 'connected-react-router';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import {
|
||||||
|
faLongArrowAltRight,
|
||||||
|
faLongArrowAltUp,
|
||||||
|
faLongArrowAltDown
|
||||||
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import backgroundVideo from '../../../common/assets/onboarding.webm';
|
||||||
|
import { _getCurrentAccount } from '../../../common/utils/selectors';
|
||||||
|
import BisectHosting from '../../../ui/BisectHosting';
|
||||||
|
import KoFiButton from '../../../common/assets/ko-fi.png';
|
||||||
|
import { openModal } from '../../../common/reducers/modals/actions';
|
||||||
|
|
||||||
|
const Background = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: ${props => props.theme.palette.colors.darkBlue};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const scrollToRef = ref =>
|
||||||
|
ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
|
||||||
|
const Home = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
|
const [initScrolled, setInitScrolled] = useState(false);
|
||||||
|
const account = useSelector(_getCurrentAccount);
|
||||||
|
|
||||||
|
const firstSlideRef = useRef(null);
|
||||||
|
const secondSlideRef = useRef(null);
|
||||||
|
const thirdSlideRef = useRef(null);
|
||||||
|
const forthSlideRef = useRef(null);
|
||||||
|
const fifthSlideRef = useRef(null);
|
||||||
|
const executeScroll = type => {
|
||||||
|
if (currentSlide + type < 0 || currentSlide + type > 5) return;
|
||||||
|
setCurrentSlide(currentSlide + type);
|
||||||
|
switch (currentSlide + type) {
|
||||||
|
case 0:
|
||||||
|
scrollToRef(firstSlideRef);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
scrollToRef(secondSlideRef);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
scrollToRef(thirdSlideRef);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
scrollToRef(forthSlideRef);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
scrollToRef(fifthSlideRef);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
scrollToRef(firstSlideRef);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
setInitScrolled(true);
|
||||||
|
executeScroll(1);
|
||||||
|
}, 4800);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Background>
|
||||||
|
<div
|
||||||
|
ref={firstSlideRef}
|
||||||
|
css={`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${props => props.theme.palette.grey[700]};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 40px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
css={`
|
||||||
|
height: 100vh;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<source src={backgroundVideo} type="video/webm" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={secondSlideRef}
|
||||||
|
css={`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${props => props.theme.palette.grey[800]};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 120px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{account.selectedProfile.name}, welcome to GDLauncher!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={thirdSlideRef}
|
||||||
|
css={`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${props => props.theme.palette.grey[700]};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20% 10%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
GDlauncher is completely free and open source. <br />
|
||||||
|
If you want to support us, consider renting a server on BisectHosting,
|
||||||
|
our official partner!
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
cursor: pointer;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<BisectHosting
|
||||||
|
showPointerCursor
|
||||||
|
size={100}
|
||||||
|
onClick={() => dispatch(openModal('BisectHosting'))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={forthSlideRef}
|
||||||
|
css={`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${props => props.theme.palette.grey[800]};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Or you can also support us through Ko-Fi.
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
margin: 40px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<a href="https://ko-fi.com/gdlauncher">
|
||||||
|
<img src={KoFiButton} alt="Ko-Fi" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={fifthSlideRef}
|
||||||
|
css={`
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: ${props => props.theme.palette.grey[700]};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
margin: 20%;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Also, don't forget to join us on Discord! This is where our
|
||||||
|
community is!
|
||||||
|
<iframe
|
||||||
|
css={`
|
||||||
|
margin-top: 40px;
|
||||||
|
`}
|
||||||
|
src="https://discordapp.com/widget?id=398091532881756161&theme=dark"
|
||||||
|
width="350"
|
||||||
|
height="410"
|
||||||
|
allowTransparency="true"
|
||||||
|
frameBorder="0"
|
||||||
|
title="discordFrame"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{currentSlide !== 0 && currentSlide !== 1 && initScrolled && (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
top: 40px;
|
||||||
|
transition: 0.1s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 70px;
|
||||||
|
height: 40px;
|
||||||
|
color: ${props => props.theme.palette.text.icon};
|
||||||
|
&:hover {
|
||||||
|
background: ${props => props.theme.action.hover};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
onClick={() => executeScroll(-1)}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faLongArrowAltUp} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{currentSlide !== 0 && initScrolled && (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
position: fixed;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
transition: 0.1s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 70px;
|
||||||
|
height: 40px;
|
||||||
|
color: ${props => props.theme.palette.text.icon};
|
||||||
|
&:hover {
|
||||||
|
background: ${props => props.theme.action.hover};
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
onClick={() => {
|
||||||
|
if (currentSlide === 4) {
|
||||||
|
dispatch(push('/home'));
|
||||||
|
} else {
|
||||||
|
executeScroll(1);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={currentSlide === 4 ? faLongArrowAltRight : faLongArrowAltDown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Background>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Home);
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button } from 'antd';
|
||||||
|
import creeper from './assets/creeper.png';
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.Component {
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
// Update state so the next render will show the fallback UI.
|
||||||
|
return { error: error.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { error: null, info: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, info) {
|
||||||
|
this.setState(prevState => {
|
||||||
|
return {
|
||||||
|
...prevState,
|
||||||
|
error: prevState.error
|
||||||
|
? `${prevState.error} / ${error.message}`
|
||||||
|
: error.message,
|
||||||
|
info: info.componentStack || prevState.info
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { error, info } = this.state;
|
||||||
|
const { children } = this.props;
|
||||||
|
if (error) {
|
||||||
|
// You can render any custom fallback UI
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
padding: 60px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<img src={creeper} alt="creeper" />
|
||||||
|
<h1
|
||||||
|
css={`
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
WE’RE SSSSSSORRY. GDLauncher ran into a creeper and blew up..
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
margin-top: 20px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{error} <br />
|
||||||
|
{info}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (process?.env?.APP_TYPE !== 'web') {
|
||||||
|
// eslint-disable-next-line global-require
|
||||||
|
require('electron').ipcRenderer.invoke('appRestart');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
css={`
|
||||||
|
margin-top: 30px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
Restart GDLauncher
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
|
export default createGlobalStyle`
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
-webkit-transform:translate3d(0,0,0);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
font-family: Inter, Roboto, Helvetica, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disable-animations *,
|
||||||
|
.disable-animations *::before,
|
||||||
|
.disable-animations *::after {
|
||||||
|
transition: none !important;
|
||||||
|
animation: none !important;
|
||||||
|
backdrop-filter: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus { outline: none; }
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border-width: 1px 0 0;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-right-style: initial;
|
||||||
|
border-bottom-style: initial;
|
||||||
|
border-left-style: initial;
|
||||||
|
border-top-color: rgba(255, 255, 255, 0.4);
|
||||||
|
border-right-color: initial;
|
||||||
|
border-bottom-color: initial;
|
||||||
|
border-left-color: initial;
|
||||||
|
border-image: initial;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-webkit-transform:translate3d(0,0,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: ${props => props.theme.palette.grey[200]};
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(input):not(textarea):not(button):not(span):not(div):not(a):not(i):not(span):not(svg):not(path),
|
||||||
|
:not(input):not(textarea):not(button):not(span):not(div):not(a):not(i):not(span):not(svg):not(path)::after,
|
||||||
|
:not(input):not(textarea):not(button):not(span):not(div):not(a):not(i):not(span):not(svg):not(path)::before {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu {
|
||||||
|
background: ${props => props.theme.palette.grey[700]};
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
font-size: 16px;
|
||||||
|
min-width: 220px;
|
||||||
|
outline: none;
|
||||||
|
transform-origin: top left;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
transition: opacity 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu.react-contextmenu--visible {
|
||||||
|
transition: opacity 150ms;
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
z-index: 999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item {
|
||||||
|
background: 0 0;
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 8px 10px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
span {
|
||||||
|
color: ${props => props.theme.palette.text.primary};
|
||||||
|
width: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item:not(.react-contextmenu-item--divider):hover{
|
||||||
|
background: ${({ theme }) => theme.palette.grey[600]};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item:not(.react-contextmenu-item--divider):active{
|
||||||
|
background: ${({ theme }) => theme.palette.grey[500]};
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item.react-contextmenu-item--disabled,
|
||||||
|
.react-contextmenu-item.react-contextmenu-item--disabled:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
color: ${props => props.theme.palette.text.disabled};
|
||||||
|
}
|
||||||
|
|
||||||
|
.react-contextmenu-item--divider {
|
||||||
|
border-bottom: 1px solid ${props => props.theme.palette.grey[600]};
|
||||||
|
cursor: inherit;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
padding: 2px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-dropdown, .ant-dropdown, .ant-cascader-menus {
|
||||||
|
z-index: 99999999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper {
|
||||||
|
color: rgba(255, 255, 255, 0.65) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper:hover, .ant-radio-button-wrapper-checked {
|
||||||
|
color: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked {
|
||||||
|
border-color: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled)::before,
|
||||||
|
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):hover::before {
|
||||||
|
background-color: rgba(255, 255, 255, 0.95) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes modalShake {
|
||||||
|
0% { transform: scale(1.01) }
|
||||||
|
30% { transform: scale(0.99) }
|
||||||
|
60% { transform: scale(1.01) }
|
||||||
|
90% { transform: scale(0.99) }
|
||||||
|
100% { transform: scale(1) }
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,362 @@
|
|||||||
|
// @flow
|
||||||
|
import axios from 'axios';
|
||||||
|
import qs from 'querystring';
|
||||||
|
import {
|
||||||
|
MOJANG_APIS,
|
||||||
|
FORGESVC_URL,
|
||||||
|
FABRIC_APIS,
|
||||||
|
JAVA_MANIFEST_URL,
|
||||||
|
IMGUR_CLIENT_ID,
|
||||||
|
MICROSOFT_LIVE_LOGIN_URL,
|
||||||
|
MICROSOFT_XBOX_LOGIN_URL,
|
||||||
|
MICROSOFT_XSTS_AUTH_URL,
|
||||||
|
MINECRAFT_SERVICES_URL,
|
||||||
|
JAVA_LATEST_MANIFEST_URL
|
||||||
|
} from './utils/constants';
|
||||||
|
import { sortByDate, getMcManifestUrl } from './utils';
|
||||||
|
import ga from './utils/analytics';
|
||||||
|
|
||||||
|
const axioInstance = axios.create({
|
||||||
|
headers: {
|
||||||
|
'X-API-KEY': '$2a$10$5BgCleD8.rLQ5Ix17Xm2lOjgfoeTJV26a1BXmmpwrOemgI517.nuC',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const trackCurseForgeAPI = () => {
|
||||||
|
ga.sendCustomEvent('CurseForgeAPICall');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Microsoft Auth
|
||||||
|
export const msExchangeCodeForAccessToken = (
|
||||||
|
clientId,
|
||||||
|
redirectUrl,
|
||||||
|
code,
|
||||||
|
codeVerifier
|
||||||
|
) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MICROSOFT_LIVE_LOGIN_URL}/oauth20_token.srf`,
|
||||||
|
qs.stringify({
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
client_id: clientId,
|
||||||
|
scope: 'offline_access xboxlive.signin xboxlive.offline_access',
|
||||||
|
redirect_uri: redirectUrl,
|
||||||
|
code,
|
||||||
|
code_verifier: codeVerifier
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'X-Skip-Origin': 'skip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const msAuthenticateXBL = accessToken => {
|
||||||
|
return axios.post(
|
||||||
|
`${MICROSOFT_XBOX_LOGIN_URL}/user/authenticate`,
|
||||||
|
{
|
||||||
|
Properties: {
|
||||||
|
AuthMethod: 'RPS',
|
||||||
|
SiteName: 'user.auth.xboxlive.com',
|
||||||
|
RpsTicket: `d=${accessToken}` // your access token from step 2 here
|
||||||
|
},
|
||||||
|
RelyingParty: 'http://auth.xboxlive.com',
|
||||||
|
TokenType: 'JWT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'x-xbl-contract-version': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const msAuthenticateXSTS = xblToken => {
|
||||||
|
return axios.post(`${MICROSOFT_XSTS_AUTH_URL}/xsts/authorize`, {
|
||||||
|
Properties: {
|
||||||
|
SandboxId: 'RETAIL',
|
||||||
|
UserTokens: [xblToken]
|
||||||
|
},
|
||||||
|
RelyingParty: 'rp://api.minecraftservices.com/',
|
||||||
|
TokenType: 'JWT'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const msAuthenticateMinecraft = (uhsToken, xstsToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MINECRAFT_SERVICES_URL}/authentication/login_with_xbox`,
|
||||||
|
{
|
||||||
|
identityToken: `XBL3.0 x=${uhsToken};${xstsToken}`
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const msMinecraftProfile = mcAccessToken => {
|
||||||
|
return axios.get(`${MINECRAFT_SERVICES_URL}/minecraft/profile`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${mcAccessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const msOAuthRefresh = (clientId, refreshToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MICROSOFT_LIVE_LOGIN_URL}/oauth20_token.srf`,
|
||||||
|
qs.stringify({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
scope: 'offline_access xboxlive.signin xboxlive.offline_access',
|
||||||
|
client_id: clientId,
|
||||||
|
refresh_token: refreshToken
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'X-Skip-Origin': 'skip'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Minecraft API
|
||||||
|
|
||||||
|
export const mcAuthenticate = (username, password, clientToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MOJANG_APIS}/authenticate`,
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
name: 'Minecraft',
|
||||||
|
version: 1
|
||||||
|
},
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
clientToken,
|
||||||
|
requestUser: true
|
||||||
|
},
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mcValidate = (accessToken, clientToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MOJANG_APIS}/validate`,
|
||||||
|
{
|
||||||
|
accessToken,
|
||||||
|
clientToken
|
||||||
|
},
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mcRefresh = (accessToken, clientToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MOJANG_APIS}/refresh`,
|
||||||
|
{
|
||||||
|
accessToken,
|
||||||
|
clientToken,
|
||||||
|
requestUser: true
|
||||||
|
},
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mcGetPlayerSkin = uuid => {
|
||||||
|
return axios.get(
|
||||||
|
`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const imgurPost = (image, onProgress) => {
|
||||||
|
const bodyFormData = new FormData();
|
||||||
|
bodyFormData.append('image', image);
|
||||||
|
|
||||||
|
return axios.post('https://api.imgur.com/3/image', bodyFormData, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Client-ID ${IMGUR_CLIENT_ID}`
|
||||||
|
},
|
||||||
|
...(onProgress && { onUploadProgress: onProgress })
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mcInvalidate = (accessToken, clientToken) => {
|
||||||
|
return axios.post(
|
||||||
|
`${MOJANG_APIS}/invalidate`,
|
||||||
|
{
|
||||||
|
accessToken,
|
||||||
|
clientToken
|
||||||
|
},
|
||||||
|
{ headers: { 'Content-Type': 'application/json' } }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMcManifest = () => {
|
||||||
|
const url = `${getMcManifestUrl()}?timestamp=${new Date().getTime()}`;
|
||||||
|
return axios.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getForgeManifest = () => {
|
||||||
|
const url = `https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json?timestamp=${new Date().getTime()}`;
|
||||||
|
return axios.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFabricManifest = () => {
|
||||||
|
const url = `${FABRIC_APIS}/versions`;
|
||||||
|
return axios.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getJavaManifest = () => {
|
||||||
|
const url = JAVA_MANIFEST_URL;
|
||||||
|
return axios.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getJavaLatestManifest = () => {
|
||||||
|
const url = JAVA_LATEST_MANIFEST_URL;
|
||||||
|
return axios.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFabricJson = ({ mcVersion, loaderVersion }) => {
|
||||||
|
return axios.get(
|
||||||
|
`${FABRIC_APIS}/versions/loader/${encodeURIComponent(
|
||||||
|
mcVersion
|
||||||
|
)}/${encodeURIComponent(loaderVersion)}/profile/json`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// FORGE ADDONS
|
||||||
|
|
||||||
|
export const getAddon = async projectID => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods/${projectID}`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getMultipleAddons = async addons => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods`;
|
||||||
|
const { data } = await axioInstance.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify({
|
||||||
|
modIds: addons
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonFiles = async projectID => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
// Aggregate results in case of multiple pages
|
||||||
|
const results = [];
|
||||||
|
let hasMore = true;
|
||||||
|
|
||||||
|
while (hasMore) {
|
||||||
|
const url = `${FORGESVC_URL}/mods/${projectID}/files?pageSize=400&index=${results.length}`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
results.push(...(data.data || []));
|
||||||
|
|
||||||
|
hasMore = data.pagination.totalCount > results.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.sort(sortByDate);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonDescription = async projectID => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods/${projectID}/description`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonFile = async (projectID, fileID) => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods/${projectID}/files/${fileID}`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonsByFingerprint = async fingerprints => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/fingerprints`;
|
||||||
|
const { data } = await axioInstance.post(url, { fingerprints });
|
||||||
|
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonFileChangelog = async (projectID, fileID) => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods/${projectID}/files/${fileID}/changelog`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAddonCategories = async () => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/categories?gameId=432`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
return data.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCFVersionIds = async () => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/games/432/versions`;
|
||||||
|
const { data } = await axioInstance.get(url);
|
||||||
|
return data.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSearch = async (
|
||||||
|
type,
|
||||||
|
searchFilter,
|
||||||
|
pageSize,
|
||||||
|
index,
|
||||||
|
sort,
|
||||||
|
isSortDescending,
|
||||||
|
gameVersion,
|
||||||
|
categoryId,
|
||||||
|
modLoaderType
|
||||||
|
) => {
|
||||||
|
trackCurseForgeAPI();
|
||||||
|
const url = `${FORGESVC_URL}/mods/search`;
|
||||||
|
|
||||||
|
// Map sort to sortField
|
||||||
|
let sortField = 1;
|
||||||
|
switch (sort) {
|
||||||
|
case 'Popularity':
|
||||||
|
sortField = 2;
|
||||||
|
break;
|
||||||
|
case 'LastUpdated':
|
||||||
|
sortField = 3;
|
||||||
|
break;
|
||||||
|
case 'Name':
|
||||||
|
sortField = 4;
|
||||||
|
break;
|
||||||
|
case 'Author':
|
||||||
|
sortField = 5;
|
||||||
|
break;
|
||||||
|
case 'TotalDownloads':
|
||||||
|
sortField = 6;
|
||||||
|
break;
|
||||||
|
case 'Featured':
|
||||||
|
default:
|
||||||
|
sortField = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
gameId: 432,
|
||||||
|
categoryId: categoryId || 0,
|
||||||
|
pageSize,
|
||||||
|
index,
|
||||||
|
sortField,
|
||||||
|
sortOrder: isSortDescending ? 'desc' : 'asc',
|
||||||
|
gameVersion: gameVersion || '',
|
||||||
|
...(modLoaderType === 'fabric' && { modLoaderType: 'Fabric' }),
|
||||||
|
classId: type === 'mods' ? 6 : 4471,
|
||||||
|
searchFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
const { data } = await axioInstance.get(url, { params });
|
||||||
|
return data?.data;
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 816 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 850 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,332 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
const AcceptableUsePolicy = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Acceptable use policy</h1>
|
||||||
|
<div>
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
This acceptable use policy (“Policy”) sets forth the general
|
||||||
|
guidelines and acceptable and prohibited uses of the{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer"
|
||||||
|
href="https://gdlauncher.com"
|
||||||
|
>
|
||||||
|
gdlauncher.com
|
||||||
|
</a>{' '}
|
||||||
|
website (“Website”), “GDLauncher” application (“Application”) and
|
||||||
|
any of their related products and services (collectively,
|
||||||
|
“Services”). This Policy is a legally binding agreement between you
|
||||||
|
(“User”, “you” or “your”) and this Website operator and Application
|
||||||
|
developer (“Operator”, “we”, “us” or “our”). If you are entering
|
||||||
|
into this agreement on behalf of a business or other legal entity,
|
||||||
|
you represent that you have the authority to bind such entity to
|
||||||
|
this agreement, in which case the terms “User”, “you” or “your”
|
||||||
|
shall refer to such entity. If you do not have such authority, or if
|
||||||
|
you do not agree with the terms of this agreement, you must not
|
||||||
|
accept this agreement and may not access and use the Services. By
|
||||||
|
accessing and using the Services, you acknowledge that you have
|
||||||
|
read, understood, and agree to be bound by the terms of this
|
||||||
|
Agreement. You acknowledge that this Agreement is a contract between
|
||||||
|
you and the Operator, even though it is electronic and is not
|
||||||
|
physically signed by you, and it governs your use of the Services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Prohibited activities and uses</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You may not use the Services to publish content or engage in
|
||||||
|
activity that is illegal under applicable law, that is harmful to
|
||||||
|
others, or that would subject us to liability, including, without
|
||||||
|
limitation, in connection with any of the following, each of which
|
||||||
|
is prohibited under this Policy:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Distributing malware or other malicious code.</li>
|
||||||
|
<li>Disclosing sensitive personal information about others.</li>
|
||||||
|
<li>
|
||||||
|
Collecting, or attempting to collect, personal information about
|
||||||
|
third parties without their knowledge or consent.
|
||||||
|
</li>
|
||||||
|
<li>Distributing pornography or adult related content.</li>
|
||||||
|
<li>
|
||||||
|
Promoting or facilitating prostitution or any escort services.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Hosting, distributing or linking to child pornography or content
|
||||||
|
that is harmful to minors.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Promoting or facilitating gambling, violence, terrorist activities
|
||||||
|
or selling weapons or ammunition.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Engaging in the unlawful distribution of controlled substances,
|
||||||
|
drug contraband or prescription medications.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Managing payment aggregators or facilitators such as processing
|
||||||
|
payments on behalf of other businesses or charities.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Facilitating pyramid schemes or other models intended to seek
|
||||||
|
payments from public actors.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Threatening harm to persons or property or otherwise harassing
|
||||||
|
behavior.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Purchasing any of the offered Services on someone else’s behalf.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Misrepresenting or fraudulently representing products or services.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Infringing the intellectual property or other proprietary rights
|
||||||
|
of others.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Facilitating, aiding, or encouraging any of the above activities
|
||||||
|
through the Services.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>System abuse</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Any User in violation of the Services security is subject to
|
||||||
|
criminal and civil liability, as well as immediate account
|
||||||
|
termination. Examples include, but are not limited to the following:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Use or distribution of tools designed for compromising security of
|
||||||
|
the Services.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Intentionally or negligently transmitting files containing a
|
||||||
|
computer virus or corrupted data.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Accessing another network without permission, including to probe
|
||||||
|
or scan for vulnerabilities or breach security or authentication
|
||||||
|
measures.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Unauthorized scanning or monitoring of data on any network or
|
||||||
|
system without proper authorization of the owner of the system or
|
||||||
|
network.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Service resources</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You may not consume excessive amounts of the resources of the
|
||||||
|
Services or use the Services in any way which results in performance
|
||||||
|
issues or which interrupts the Services for other Users. Prohibited
|
||||||
|
activities that contribute to excessive use, include without
|
||||||
|
limitation:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Deliberate attempts to overload the Services and broadcast attacks
|
||||||
|
(i.e. denial of service attacks).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Engaging in any other activities that degrade the usability and
|
||||||
|
performance of the Services.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Hosting or running malicious code or other scripts or processes
|
||||||
|
that adversely impact the Services.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Operating a file sharing site or scripts for BitTorrent or
|
||||||
|
similar, which includes sending or receiving files containing
|
||||||
|
these mechanisms.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Web proxy scripts, such as those that allow anyone to browse to a
|
||||||
|
third party website anonymously, are prohibited.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>No spam policy</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You may not use the Services to send spam or bulk unsolicited
|
||||||
|
messages. We maintain a zero tolerance policy for use of the
|
||||||
|
Services in any manner associated with the transmission,
|
||||||
|
distribution or delivery of any bulk e-mail, including unsolicited
|
||||||
|
bulk or unsolicited commercial e-mail, or the sending, assisting, or
|
||||||
|
commissioning the transmission of commercial e-mail that does not
|
||||||
|
comply with the U.S. CAN-SPAM Act of 2003 (“SPAM”).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Your products or services advertised via SPAM (i.e. Spamvertised)
|
||||||
|
may not be used in conjunction with the Services. This provision
|
||||||
|
includes, but is not limited to, SPAM sent via fax, phone, postal
|
||||||
|
mail, email, instant messaging, or newsgroups.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Sending emails through the Services to purchased email lists (“safe
|
||||||
|
lists”) will be treated as SPAM.
|
||||||
|
</p>
|
||||||
|
<h2>Defamation and objectionable content</h2>
|
||||||
|
<p>
|
||||||
|
We value the freedom of expression and encourage Users to be
|
||||||
|
respectful with the content they post. We are not a publisher of
|
||||||
|
User content and are not in a position to investigate the veracity
|
||||||
|
of individual defamation claims or to determine whether certain
|
||||||
|
material, which we may find objectionable, should be censored.
|
||||||
|
However, we reserve the right to moderate, disable or remove any
|
||||||
|
content to prevent harm to others or to us or the Services, as
|
||||||
|
determined in our sole discretion.
|
||||||
|
</p>
|
||||||
|
<h2>Copyrighted content</h2>
|
||||||
|
<p>
|
||||||
|
Copyrighted material must not be published via the Services without
|
||||||
|
the explicit permission of the copyright owner or a person
|
||||||
|
explicitly authorized to give such permission by the copyright
|
||||||
|
owner. Upon receipt of a claim for copyright infringement, or a
|
||||||
|
notice of such violation, we will immediately run full investigation
|
||||||
|
and, upon confirmation, will notify the person or persons
|
||||||
|
responsible for publishing it and, in our sole discretion, will
|
||||||
|
remove the infringing material from the Services. We may terminate
|
||||||
|
the Service of Users with repeated copyright infringements. Further
|
||||||
|
procedures may be carried out if necessary. We will assume no
|
||||||
|
liability to any User of the Services for the removal of any such
|
||||||
|
material. If you believe your copyright is being infringed by a
|
||||||
|
person or persons using the Services, please get in touch with us to
|
||||||
|
report copyright infringement.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Security</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You take full responsibility for maintaining reasonable security
|
||||||
|
precautions for your account. You are responsible for protecting and
|
||||||
|
updating any login account provided to you for the Services. You
|
||||||
|
must protect the confidentiality of your login details, and you
|
||||||
|
should change your password periodically. You are responsible for
|
||||||
|
ensuring all User provided software installed by you on the Services
|
||||||
|
is updated and patched following industry best practice. We make no
|
||||||
|
warranty express or implied for the security and operability of 3rd
|
||||||
|
party software or scripts installed or run by you on the Services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Enforcement</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We reserve our right to be the sole arbiter in determining the
|
||||||
|
seriousness of each infringement and to immediately take corrective
|
||||||
|
actions, including but not limited to:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Suspending or terminating your Service with or without notice upon
|
||||||
|
any violation of this Policy. Any violations may also result in
|
||||||
|
the immediate suspension or termination of your account.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Disabling or removing any content which is prohibited by this
|
||||||
|
Policy, including to prevent harm to others or to us or the
|
||||||
|
Services, as determined by us in our sole discretion.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Reporting violations to law enforcement as determined by us in our
|
||||||
|
sole discretion.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
A failure to respond to an email from our abuse team within 2
|
||||||
|
days, or as otherwise specified in the communication to you, may
|
||||||
|
result in the suspension or termination of your account.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Suspended and terminated User accounts due to violations will not be
|
||||||
|
re-activated.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Nothing contained in this Policy shall be construed to limit our
|
||||||
|
actions or remedies in any way with respect to any of the prohibited
|
||||||
|
activities. In addition, we reserve at all times all rights and
|
||||||
|
remedies available to us with respect to such activities at law or
|
||||||
|
in equity.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Reporting violations</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
If you have discovered and would like to report a violation of this
|
||||||
|
Policy, please contact us immediately. We will investigate the
|
||||||
|
situation and provide you with full assistance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Changes and amendments</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We reserve the right to modify this Policy or its terms related to
|
||||||
|
the Services at any time at our discretion. When we do, we will
|
||||||
|
revise the updated date at the bottom of this page. We may also
|
||||||
|
provide notice to you in other ways at our discretion, such as
|
||||||
|
through the contact information you have provided.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
An updated version of this Policy will be effective immediately upon
|
||||||
|
the posting of the revised Policy unless otherwise specified. Your
|
||||||
|
continued use of the Services after the effective date of the
|
||||||
|
revised Policy (or such other act specified at that time) will
|
||||||
|
constitute your consent to those changes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Acceptance of this policy</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You acknowledge that you have read this Policy and agree to all its
|
||||||
|
terms and conditions. By accessing and using the Services you agree
|
||||||
|
to be bound by this Policy. If you do not agree to abide by the
|
||||||
|
terms of this Policy, you are not authorized to access or use the
|
||||||
|
Services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Contacting us</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
If you have any questions, concerns, or complaints regarding this
|
||||||
|
Policy, we encourage you to contact us using the details below:
|
||||||
|
</p>
|
||||||
|
<p>info@gdevs.io</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>This document was last updated on August 16, 2021</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(AcceptableUsePolicy);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import React, { Suspense } from 'react';
|
||||||
|
|
||||||
|
function WaitingComponent(MyComponent) {
|
||||||
|
return props => (
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<MyComponent {...props} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WaitingComponent;
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faWindowClose } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
const CloseButton = props => {
|
||||||
|
return (
|
||||||
|
// eslint-disable-next-line
|
||||||
|
<CloseIcon {...props}>
|
||||||
|
<FontAwesomeIcon icon={faWindowClose} />
|
||||||
|
</CloseIcon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CloseButton;
|
||||||
|
|
||||||
|
const CloseIcon = styled.div`
|
||||||
|
font-size: 20px;
|
||||||
|
width: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.palette.error.main};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { useKey } from 'rooks';
|
||||||
|
import CloseButton from './CloseButton';
|
||||||
|
import { closeModal } from '../reducers/modals/actions';
|
||||||
|
|
||||||
|
const HeaderComponent = styled.div`
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 10px;
|
||||||
|
height: 40px;
|
||||||
|
background: ${props => props.theme.palette.grey[800]};
|
||||||
|
border-radius: 4px;
|
||||||
|
h3 {
|
||||||
|
line-height: 40px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Modal = ({
|
||||||
|
transparentBackground,
|
||||||
|
header,
|
||||||
|
title,
|
||||||
|
backBtn,
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
removePadding,
|
||||||
|
closeCallback,
|
||||||
|
preventClose
|
||||||
|
}) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const closeFunc = () => {
|
||||||
|
if (closeCallback) closeCallback();
|
||||||
|
dispatch(closeModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
useKey(['Escape'], () => {
|
||||||
|
if (!preventClose) closeFunc();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onMouseDown={e => e.stopPropagation()}
|
||||||
|
transparentBackground={transparentBackground}
|
||||||
|
className={className}
|
||||||
|
css={`
|
||||||
|
background: ${props =>
|
||||||
|
props.transparentBackground
|
||||||
|
? 'transparent'
|
||||||
|
: props.theme.palette.grey[700]};
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 4px;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
{(header === undefined || header === true) && (
|
||||||
|
<HeaderComponent>
|
||||||
|
<h3>{title || 'Modal'}</h3>
|
||||||
|
{!preventClose && <CloseButton onClick={closeFunc} />}
|
||||||
|
</HeaderComponent>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
header={header}
|
||||||
|
removePadding={removePadding}
|
||||||
|
css={`
|
||||||
|
height: ${header === undefined || header === true
|
||||||
|
? 'calc(100% - 40px)'
|
||||||
|
: '100%'};
|
||||||
|
width: 100%;
|
||||||
|
padding: ${props =>
|
||||||
|
(props.header === undefined || props.header === true) &&
|
||||||
|
!props.removePadding
|
||||||
|
? 20
|
||||||
|
: 0}px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<span onClick={closeFunc}>{backBtn !== undefined && backBtn}</span>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(Modal);
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import React, { useState, useEffect, lazy } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { closeModal } from '../reducers/modals/actions';
|
||||||
|
import AsyncComponent from './AsyncComponent';
|
||||||
|
import Settings from '../modals/Settings';
|
||||||
|
|
||||||
|
const Overlay = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: ${props => props.theme.sizes.height.systemNavbar}px;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
will-change: opacity;
|
||||||
|
transition: opacity 300ms cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
z-index: 9999999;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Modal = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: transparent;
|
||||||
|
transition: transform 300ms;
|
||||||
|
will-change: transform;
|
||||||
|
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
z-index: 9999999;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const modalsComponentLookupTable = {
|
||||||
|
AddInstance: AsyncComponent(lazy(() => import('../modals/AddInstance'))),
|
||||||
|
AccountsManager: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/AccountsManager'))
|
||||||
|
),
|
||||||
|
Settings,
|
||||||
|
Screenshot: AsyncComponent(lazy(() => import('../modals/Screenshot'))),
|
||||||
|
InstanceDeleteConfirmation: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceDeleteConfirmation'))
|
||||||
|
),
|
||||||
|
ActionConfirmation: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/ActionConfirmation'))
|
||||||
|
),
|
||||||
|
AddAccount: AsyncComponent(lazy(() => import('../modals/AddAccount'))),
|
||||||
|
ModpackDescription: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/ModpackDescription'))
|
||||||
|
),
|
||||||
|
InstanceManager: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceManager'))
|
||||||
|
),
|
||||||
|
InstanceExportCurseForge: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceExport/CurseForge'))
|
||||||
|
),
|
||||||
|
InstanceDuplicateName: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceDuplicateName'))
|
||||||
|
),
|
||||||
|
AutoUpdatesNotAvailable: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/AutoUpdatesNotAvailable'))
|
||||||
|
),
|
||||||
|
OptedOutModsList: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/OptedOutModsList'))
|
||||||
|
),
|
||||||
|
BisectHosting: AsyncComponent(lazy(() => import('../modals/BisectHosting'))),
|
||||||
|
Onboarding: AsyncComponent(lazy(() => import('../modals/Onboarding'))),
|
||||||
|
ModOverview: AsyncComponent(lazy(() => import('../modals/ModOverview'))),
|
||||||
|
ModChangelog: AsyncComponent(lazy(() => import('../modals/ModChangelog'))),
|
||||||
|
ModsBrowser: AsyncComponent(lazy(() => import('../modals/ModsBrowser'))),
|
||||||
|
JavaSetup: AsyncComponent(lazy(() => import('../modals/JavaSetup'))),
|
||||||
|
ModsUpdater: AsyncComponent(lazy(() => import('../modals/ModsUpdater'))),
|
||||||
|
InstanceCrashed: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceCrashed'))
|
||||||
|
),
|
||||||
|
ChangeLogs: AsyncComponent(lazy(() => import('../modals/ChangeLogs'))),
|
||||||
|
McVersionChanger: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/McVersionChanger'))
|
||||||
|
),
|
||||||
|
PolicyModal: AsyncComponent(lazy(() => import('../modals/PolicyModal'))),
|
||||||
|
InstanceStartupAd: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceStartupAd'))
|
||||||
|
),
|
||||||
|
InstanceDownloadFailed: AsyncComponent(
|
||||||
|
lazy(() => import('../modals/InstanceDownloadFailed'))
|
||||||
|
),
|
||||||
|
InfoModal: AsyncComponent(lazy(() => import('../modals/InfoModal')))
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalContainer = ({
|
||||||
|
unmounting,
|
||||||
|
children,
|
||||||
|
preventClose,
|
||||||
|
abortCallback
|
||||||
|
}) => {
|
||||||
|
const [modalStyle, setModalStyle] = useState({
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
const [bgStyle, setBgStyle] = useState({
|
||||||
|
background: 'rgba(0, 0, 0, 0.70)',
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(mountStyle, 0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (unmounting) unMountStyle();
|
||||||
|
}, [unmounting]);
|
||||||
|
|
||||||
|
const back = async e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (preventClose) {
|
||||||
|
setModalStyle({
|
||||||
|
animation: `modalShake 0.25s linear infinite`
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setModalStyle({
|
||||||
|
transform: 'scale(1)'
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (abortCallback) await abortCallback();
|
||||||
|
dispatch(closeModal());
|
||||||
|
};
|
||||||
|
|
||||||
|
const unMountStyle = () => {
|
||||||
|
// css for unmount animation
|
||||||
|
setModalStyle({
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
setBgStyle({
|
||||||
|
background: 'rgba(0, 0, 0, 0.70)',
|
||||||
|
opacity: 0
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const mountStyle = () => {
|
||||||
|
// css for mount animation
|
||||||
|
setModalStyle({
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
setBgStyle({
|
||||||
|
background: 'rgba(0, 0, 0, 0.70)',
|
||||||
|
opacity: 1
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Overlay onMouseDown={back} style={bgStyle}>
|
||||||
|
<Modal style={modalStyle}>{children}</Modal>
|
||||||
|
</Overlay>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalsManager = () => {
|
||||||
|
const currentModals = useSelector(state => state.modals);
|
||||||
|
|
||||||
|
const renderedModals = currentModals.map(modalDescription => {
|
||||||
|
const { modalType, modalProps = {}, unmounting = false } = modalDescription;
|
||||||
|
const ModalComponent = modalsComponentLookupTable[modalType];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContainer
|
||||||
|
unmounting={unmounting}
|
||||||
|
key={modalType}
|
||||||
|
preventClose={modalProps.preventClose}
|
||||||
|
abortCallback={modalProps.abortCallback}
|
||||||
|
modalType={modalType}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
|
||||||
|
<ModalComponent {...modalProps} />
|
||||||
|
</ModalContainer>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return renderedModals;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalsManager;
|
||||||
@@ -0,0 +1,738 @@
|
|||||||
|
import React, { memo } from 'react';
|
||||||
|
|
||||||
|
const PrivacyPolicy = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<div>
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We respect your privacy and are committed to protecting it through
|
||||||
|
our compliance with this privacy policy (“Policy”). This Policy
|
||||||
|
describes the types of information we may collect from you or that
|
||||||
|
you may provide (“Personal Information”) on the{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="nofollow noreferrer"
|
||||||
|
href="https://gdlauncher.com"
|
||||||
|
>
|
||||||
|
gdlauncher.com
|
||||||
|
</a>{' '}
|
||||||
|
website (“Website”), “GDLauncher” application (“Application”), and
|
||||||
|
any of their related products and services (collectively,
|
||||||
|
“Services”), and our practices for collecting, using, maintaining,
|
||||||
|
protecting, and disclosing that Personal Information. It also
|
||||||
|
describes the choices available to you regarding our use of your
|
||||||
|
Personal Information and how you can access and update it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This Policy is a legally binding agreement between you (“User”,
|
||||||
|
“you” or “your”) and this Website operator and Application developer
|
||||||
|
(“Operator”, “we”, “us” or “our”). If you are entering into this
|
||||||
|
agreement on behalf of a business or other legal entity, you
|
||||||
|
represent that you have the authority to bind such entity to this
|
||||||
|
agreement, in which case the terms “User”, “you” or “your” shall
|
||||||
|
refer to such entity. If you do not have such authority, or if you
|
||||||
|
do not agree with the terms of this agreement, you must not accept
|
||||||
|
this agreement and may not access and use the Services. By accessing
|
||||||
|
and using the Services, you acknowledge that you have read,
|
||||||
|
understood, and agree to be bound by the terms of this Policy. This
|
||||||
|
Policy does not apply to the practices of companies that we do not
|
||||||
|
own or control, or to individuals that we do not employ or manage.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Automatic collection of information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
When you open the Website or use the Application, our servers
|
||||||
|
automatically record information that your browser or device sends.
|
||||||
|
This data may include information such as your device’s IP address
|
||||||
|
and location, browser and device name and version, operating system
|
||||||
|
type and version, language preferences, the webpage you were
|
||||||
|
visiting before you came to the Services, pages of the Services that
|
||||||
|
you visit, the time spent on those pages, the information you search
|
||||||
|
for on the Services, access times and dates, and other statistics.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Information collected automatically is used only to identify
|
||||||
|
potential cases of abuse and establish statistical information
|
||||||
|
regarding the usage and traffic of the Services. This statistical
|
||||||
|
information is not otherwise aggregated in such a way that would
|
||||||
|
identify any particular User of the system.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Collection of personal information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You can access and use the Services without telling us who you are
|
||||||
|
or revealing any information by which someone could identify you as
|
||||||
|
a specific, identifiable individual. If, however, you wish to use
|
||||||
|
some of the features offered on the Services, you may be asked to
|
||||||
|
provide certain Personal Information (for example, your name and
|
||||||
|
e-mail address).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We receive and store any information you knowingly provide to us
|
||||||
|
when you create an account, publish content, make a purchase, or
|
||||||
|
fill any online forms on the Services. When required, this
|
||||||
|
information may include the following:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Account details (such as user name, unique user ID, password, etc)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Contact information (such as email address, phone number, etc)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Any other materials you willingly submit to us (such as articles,
|
||||||
|
images, feedback, etc)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Some of the information we collect is directly from you via the
|
||||||
|
Services. However, we may also collect Personal Information about
|
||||||
|
you from other sources such as public databases, social media
|
||||||
|
platforms, third-party data providers, and our joint marketing
|
||||||
|
partners. Personal Information we collect from other sources may
|
||||||
|
include demographic information, such as age and gender, device
|
||||||
|
information, such as IP addresses, location, such as city and state,
|
||||||
|
and online behavioral data, such as information about your use of
|
||||||
|
social media websites, page view information and search results and
|
||||||
|
links.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You can choose not to provide us with your Personal Information, but
|
||||||
|
then you may not be able to take advantage of some of the features
|
||||||
|
on the Services. Users who are uncertain about what information is
|
||||||
|
mandatory are welcome to contact us.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Privacy of children</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We do not knowingly collect any Personal Information from children
|
||||||
|
under the age of 13. If you are under the age of 13, please do not
|
||||||
|
submit any Personal Information through the Services. If you have
|
||||||
|
reason to believe that a child under the age of 13 has provided
|
||||||
|
Personal Information to us through the Services, please contact us
|
||||||
|
to request that we delete that child’s Personal Information from our
|
||||||
|
Services.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We encourage parents and legal guardians to monitor their children’s
|
||||||
|
Internet usage and to help enforce this Policy by instructing their
|
||||||
|
children never to provide Personal Information through the Services
|
||||||
|
without their permission. We also ask that all parents and legal
|
||||||
|
guardians overseeing the care of children take the necessary
|
||||||
|
precautions to ensure that their children are instructed to never
|
||||||
|
give out Personal Information when online without their permission.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Use and processing of collected information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We act as a data controller and a data processor in terms of the
|
||||||
|
GDPR when handling Personal Information, unless we have entered into
|
||||||
|
a data processing agreement with you in which case you would be the
|
||||||
|
data controller and we would be the data processor.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Our role may also differ depending on the specific situation
|
||||||
|
involving Personal Information. We act in the capacity of a data
|
||||||
|
controller when we ask you to submit your Personal Information that
|
||||||
|
is necessary to ensure your access and use of the Services. In such
|
||||||
|
instances, we are a data controller because we determine the
|
||||||
|
purposes and means of the processing of Personal Information and we
|
||||||
|
comply with data controllers’ obligations set forth in the GDPR.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We act in the capacity of a data processor in situations when you
|
||||||
|
submit Personal Information through the Services. We do not own,
|
||||||
|
control, or make decisions about the submitted Personal Information,
|
||||||
|
and such Personal Information is processed only in accordance with
|
||||||
|
your instructions. In such instances, the User providing Personal
|
||||||
|
Information acts as a data controller in terms of the GDPR.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In order to make the Services available to you, or to meet a legal
|
||||||
|
obligation, we may need to collect and use certain Personal
|
||||||
|
Information. If you do not provide the information that we request,
|
||||||
|
we may not be able to provide you with the requested products or
|
||||||
|
services. Any of the information we collect from you may be used for
|
||||||
|
the following purposes:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>Create and manage user accounts</li>
|
||||||
|
<li>Deliver products or services</li>
|
||||||
|
<li>Improve products and services</li>
|
||||||
|
<li>Send product and service updates</li>
|
||||||
|
<li>Respond to inquiries and offer support</li>
|
||||||
|
<li>Request user feedback</li>
|
||||||
|
<li>Improve user experience</li>
|
||||||
|
<li>Protect from abuse and malicious users</li>
|
||||||
|
<li>Run and operate the Services</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Processing your Personal Information depends on how you interact
|
||||||
|
with the Services, where you are located in the world and if one of
|
||||||
|
the following applies: (i) you have given your consent for one or
|
||||||
|
more specific purposes; this, however, does not apply, whenever the
|
||||||
|
processing of Personal Information is subject to California Consumer
|
||||||
|
Privacy Act or European data protection law; (ii) provision of
|
||||||
|
information is necessary for the performance of an agreement with
|
||||||
|
you and/or for any pre-contractual obligations thereof; (iii)
|
||||||
|
processing is necessary for compliance with a legal obligation to
|
||||||
|
which you are subject; (iv) processing is related to a task that is
|
||||||
|
carried out in the public interest or in the exercise of official
|
||||||
|
authority vested in us; (v) processing is necessary for the purposes
|
||||||
|
of the legitimate interests pursued by us or by a third party.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We rely on the following legal bases as defined in the GDPR upon
|
||||||
|
which we collect and process your Personal Information:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>User’s consent</li>
|
||||||
|
<li>Performance of a contract</li>
|
||||||
|
<li>Our own legitimate interests</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Note that under some legislations we may be allowed to process
|
||||||
|
information until you object to such processing by opting out,
|
||||||
|
without having to rely on consent or any other of the legal bases
|
||||||
|
above. In any case, we will be happy to clarify the specific legal
|
||||||
|
basis that applies to the processing, and in particular whether the
|
||||||
|
provision of Personal Information is a statutory or contractual
|
||||||
|
requirement, or a requirement necessary to enter into a contract.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Payment processing</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
In case of Services requiring payment, you may need to provide your
|
||||||
|
credit card details or other payment account information, which will
|
||||||
|
be used solely for processing payments. We use third-party payment
|
||||||
|
processors (“Payment Processors”) to assist us in processing your
|
||||||
|
payment information securely.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Payment Processors adhere to the latest security standards as
|
||||||
|
managed by the PCI Security Standards Council, which is a joint
|
||||||
|
effort of brands like Visa, MasterCard, American Express and
|
||||||
|
Discover. Sensitive and private data exchange happens over a SSL
|
||||||
|
secured communication channel and is encrypted and protected with
|
||||||
|
digital signatures, and the Services are also in compliance with
|
||||||
|
strict vulnerability standards in order to create as secure of an
|
||||||
|
environment as possible for Users. We will share payment data with
|
||||||
|
the Payment Processors only to the extent necessary for the purposes
|
||||||
|
of processing your payments, refunding such payments, and dealing
|
||||||
|
with complaints and queries related to such payments and refunds.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please note that the Payment Processors may collect some Personal
|
||||||
|
Information from you, which allows them to process your payments
|
||||||
|
(e.g., your email address, address, credit card details, and bank
|
||||||
|
account number) and handle all the steps in the payment process
|
||||||
|
through their systems, including data collection and data
|
||||||
|
processing. The Payment Processors’ use of your Personal Information
|
||||||
|
is governed by their respective privacy policies which may or may
|
||||||
|
not contain privacy protections as protective as this Policy. We
|
||||||
|
suggest that you review their respective privacy policies.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Managing information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You are able to delete certain Personal Information we have about
|
||||||
|
you. The Personal Information you can delete may change as the
|
||||||
|
Services change. When you delete Personal Information, however, we
|
||||||
|
may maintain a copy of the unrevised Personal Information in our
|
||||||
|
records for the duration necessary to comply with our obligations to
|
||||||
|
our affiliates and partners, and for the purposes described below.
|
||||||
|
If you would like to delete your Personal Information or permanently
|
||||||
|
delete your account, you can do so by contacting us.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Disclosure of information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Depending on the requested Services or as necessary to complete any
|
||||||
|
transaction or provide any Service you have requested, we may share
|
||||||
|
your information with our affiliates, contracted companies, and
|
||||||
|
service providers (collectively, “Service Providers”) we rely upon
|
||||||
|
to assist in the operation of the Services available to you and
|
||||||
|
whose privacy policies are consistent with ours or who agree to
|
||||||
|
abide by our policies with respect to Personal Information. We will
|
||||||
|
not share any personally identifiable information with third parties
|
||||||
|
and will not share any information with unaffiliated third parties.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Service Providers are not authorized to use or disclose your
|
||||||
|
information except as necessary to perform services on our behalf or
|
||||||
|
comply with legal requirements. Service Providers are given the
|
||||||
|
information they need only in order to perform their designated
|
||||||
|
functions, and we do not authorize them to use or disclose any of
|
||||||
|
the provided information for their own marketing or other purposes.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Retention of information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We will retain and use your Personal Information for the period
|
||||||
|
necessary to comply with our legal obligations, as long as your user
|
||||||
|
account remains active, to enforce our agreements, resolve disputes,
|
||||||
|
and unless a longer retention period is required or permitted by
|
||||||
|
law.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may use any aggregated data derived from or incorporating your
|
||||||
|
Personal Information after you update or delete it, but not in a
|
||||||
|
manner that would identify you personally. Once the retention period
|
||||||
|
expires, Personal Information shall be deleted. Therefore, the right
|
||||||
|
to access, the right to erasure, the right to rectification, and the
|
||||||
|
right to data portability cannot be enforced after the expiration of
|
||||||
|
the retention period.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Transfer of information</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Depending on your location, data transfers may involve transferring
|
||||||
|
and storing your information in a country other than your own. The
|
||||||
|
transfer of your Personal Information to countries outside the
|
||||||
|
European Union will be made only if you have explicitly consented to
|
||||||
|
it or in the cases provided for by the GDPR and will be processed in
|
||||||
|
your interest.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You are entitled to learn about the legal basis of information
|
||||||
|
transfers to a country outside the European Union or to any
|
||||||
|
international organization governed by public international law or
|
||||||
|
set up by two or more countries, such as the UN, and about the
|
||||||
|
security measures taken by us to safeguard your information. If any
|
||||||
|
such transfer takes place, you can find out more by checking the
|
||||||
|
relevant sections of this Policy or inquire with us using the
|
||||||
|
information provided in the contact section.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Data protection rights under the GDPR</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
If you are a resident of the European Economic Area (“EEA”), you
|
||||||
|
have certain data protection rights and we aim to take reasonable
|
||||||
|
steps to allow you to correct, amend, delete, or limit the use of
|
||||||
|
your Personal Information. If you wish to be informed what Personal
|
||||||
|
Information we hold about you and if you want it to be removed from
|
||||||
|
our systems, please contact us. In certain circumstances, you have
|
||||||
|
the following data protection rights:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(i) You have the right to withdraw consent where you have previously
|
||||||
|
given your consent to the processing of your Personal Information.
|
||||||
|
To the extent that the legal basis for our processing of your
|
||||||
|
Personal Information is consent, you have the right to withdraw that
|
||||||
|
consent at any time. Withdrawal will not affect the lawfulness of
|
||||||
|
processing before the withdrawal.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(ii) You have the right to learn if your Personal Information is
|
||||||
|
being processed by us, obtain disclosure regarding certain aspects
|
||||||
|
of the processing, and obtain a copy of your Personal Information
|
||||||
|
undergoing processing.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(iii) You have the right to verify the accuracy of your information
|
||||||
|
and ask for it to be updated or corrected. You also have the right
|
||||||
|
to request us to complete the Personal Information you believe is
|
||||||
|
incomplete.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(iv) You have the right to object to the processing of your
|
||||||
|
information if the processing is carried out on a legal basis other
|
||||||
|
than consent. Where Personal Information is processed for the public
|
||||||
|
interest, in the exercise of an official authority vested in us, or
|
||||||
|
for the purposes of the legitimate interests pursued by us, you may
|
||||||
|
object to such processing by providing a ground related to your
|
||||||
|
particular situation to justify the objection. You must know that,
|
||||||
|
however, should your Personal Information be processed for direct
|
||||||
|
marketing purposes, you can object to that processing at any time
|
||||||
|
without providing any justification. To learn whether we are
|
||||||
|
processing Personal Information for direct marketing purposes, you
|
||||||
|
may refer to the relevant sections of this Policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(v) You have the right, under certain circumstances, to restrict the
|
||||||
|
processing of your Personal Information. These circumstances
|
||||||
|
include: the accuracy of your Personal Information is contested by
|
||||||
|
you and we must verify its accuracy; the processing is unlawful, but
|
||||||
|
you oppose the erasure of your Personal Information and request the
|
||||||
|
restriction of its use instead; we no longer need your Personal
|
||||||
|
Information for the purposes of processing, but you require it to
|
||||||
|
establish, exercise or defend your legal claims; you have objected
|
||||||
|
to processing pending the verification of whether our legitimate
|
||||||
|
grounds override your legitimate grounds. Where processing has been
|
||||||
|
restricted, such Personal Information will be marked accordingly
|
||||||
|
and, with the exception of storage, will be processed only with your
|
||||||
|
consent or for the establishment, to exercise or defense of legal
|
||||||
|
claims, for the protection of the rights of another natural, or
|
||||||
|
legal person or for reasons of important public interest.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(vi) You have the right, under certain circumstances, to obtain the
|
||||||
|
erasure of your Personal Information from us. These circumstances
|
||||||
|
include: the Personal Information is no longer necessary in relation
|
||||||
|
to the purposes for which it was collected or otherwise processed;
|
||||||
|
you withdraw consent to consent-based processing; you object to the
|
||||||
|
processing under certain rules of applicable data protection law;
|
||||||
|
the processing is for direct marketing purposes; and the personal
|
||||||
|
data have been unlawfully processed. However, there are exclusions
|
||||||
|
of the right to erasure such as where processing is necessary: for
|
||||||
|
exercising the right of freedom of expression and information; for
|
||||||
|
compliance with a legal obligation; or for the establishment, to
|
||||||
|
exercise or defense of legal claims.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(vii) You have the right to receive your Personal Information that
|
||||||
|
you have provided to us in a structured, commonly used, and
|
||||||
|
machine-readable format and, if technically feasible, to have it
|
||||||
|
transmitted to another controller without any hindrance from us,
|
||||||
|
provided that such transmission does not adversely affect the rights
|
||||||
|
and freedoms of others.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
(viii) You have the right to complain to a data protection authority
|
||||||
|
about our collection and use of your Personal Information. If you
|
||||||
|
are not satisfied with the outcome of your complaint directly with
|
||||||
|
us, you have the right to lodge a complaint with your local data
|
||||||
|
protection authority. For more information, please contact your
|
||||||
|
local data protection authority in the EEA. This provision is
|
||||||
|
applicable provided that your Personal Information is processed by
|
||||||
|
automated means and that the processing is based on your consent, on
|
||||||
|
a contract which you are part of, or on pre-contractual obligations
|
||||||
|
thereof.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>California privacy rights</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Consumers residing in California are afforded certain additional
|
||||||
|
rights with respect to their Personal Information under the
|
||||||
|
California Consumer Privacy Act (“CCPA”). If you are a California
|
||||||
|
resident, this section applies to you.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In addition to the rights as explained in this Policy, California
|
||||||
|
residents who provide Personal Information as defined in the statute
|
||||||
|
to obtain Services for personal, family, or household use are
|
||||||
|
entitled to request and obtain from us, once a calendar year,
|
||||||
|
information about the categories and specific pieces of Personal
|
||||||
|
Information we have collected and disclosed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Furthermore, California residents have the right to request deletion
|
||||||
|
of their Personal Information or opt-out of the sale of their
|
||||||
|
Personal Information which may include selling, disclosing, or
|
||||||
|
transferring Personal Information to another business or a third
|
||||||
|
party for monetary or other valuable consideration. To do so, simply
|
||||||
|
contact us. We will not discriminate against you if you exercise
|
||||||
|
your rights under the CCPA.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>How to exercise your rights</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Any requests to exercise your rights can be directed to us through
|
||||||
|
the contact details provided in this document. Please note that we
|
||||||
|
may ask you to verify your identity before responding to such
|
||||||
|
requests. Your request must provide sufficient information that
|
||||||
|
allows us to verify that you are the person you are claiming to be
|
||||||
|
or that you are the authorized representative of such person. If we
|
||||||
|
receive your request from an authorized representative, we may
|
||||||
|
request evidence that you have provided such an authorized
|
||||||
|
representative with power of attorney or that the authorized
|
||||||
|
representative otherwise has valid written authority to submit
|
||||||
|
requests on your behalf.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You must include sufficient details to allow us to properly
|
||||||
|
understand the request and respond to it. We cannot respond to your
|
||||||
|
request or provide you with Personal Information unless we first
|
||||||
|
verify your identity or authority to make such a request and confirm
|
||||||
|
that the Personal Information relates to you.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Cookies</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Our Services use “cookies” to help personalize your online
|
||||||
|
experience. A cookie is a text file that is placed on your hard disk
|
||||||
|
by a web page server. Cookies cannot be used to run programs or
|
||||||
|
deliver viruses to your computer. Cookies are uniquely assigned to
|
||||||
|
you, and can only be read by a web server in the domain that issued
|
||||||
|
the cookie to you. If you choose to decline cookies, you may not be
|
||||||
|
able to fully experience the features of the Services. You may learn
|
||||||
|
more about cookies and how they work{' '}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
href="https://www.websitepolicies.com/blog/cookies"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may use cookies to collect, store, and track information for
|
||||||
|
security and personalization, to operate the Services, and for
|
||||||
|
statistical purposes. Please note that you have the ability to
|
||||||
|
accept or decline cookies. Most web browsers automatically accept
|
||||||
|
cookies by default, but you can modify your browser settings to
|
||||||
|
decline cookies if you prefer.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Data analytics</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Our Services may use third-party analytics tools that use cookies,
|
||||||
|
web beacons, or other similar information-gathering technologies to
|
||||||
|
collect standard internet activity and usage information. The
|
||||||
|
information gathered is used to compile statistical reports on User
|
||||||
|
activity such as how often Users visit our Services, what pages they
|
||||||
|
visit and for how long, etc. We use the information obtained from
|
||||||
|
these analytics tools to monitor the performance and improve our
|
||||||
|
Services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Do Not Track signals</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Some browsers incorporate a Do Not Track feature that signals to
|
||||||
|
websites you visit that you do not want to have your online activity
|
||||||
|
tracked. Tracking is not the same as using or collecting information
|
||||||
|
in connection with a website. For these purposes, tracking refers to
|
||||||
|
collecting personally identifiable information from consumers who
|
||||||
|
use or visit a website or online service as they move across
|
||||||
|
different websites over time. How browsers communicate the Do Not
|
||||||
|
Track signal is not yet uniform. As a result, the Services are not
|
||||||
|
yet set up to interpret or respond to Do Not Track signals
|
||||||
|
communicated by your browser. Even so, as described in more detail
|
||||||
|
throughout this Policy, we limit our use and collection of your
|
||||||
|
Personal Information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Advertisements</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We may display online advertisements and we may share aggregated and
|
||||||
|
non-identifying information about our customers that we or our
|
||||||
|
advertisers collect through your use of the Services. We do not
|
||||||
|
share personally identifiable information about individual customers
|
||||||
|
with advertisers. In some instances, we may use this aggregated and
|
||||||
|
non-identifying information to deliver tailored advertisements to
|
||||||
|
the intended audience.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Social media features</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Our Services may include social media features, such as the Facebook
|
||||||
|
and Twitter buttons, Share This buttons, etc (collectively, “Social
|
||||||
|
Media Features”). These Social Media Features may collect your IP
|
||||||
|
address, what page you are visiting on our Services, and may set a
|
||||||
|
cookie to enable Social Media Features to function properly. Social
|
||||||
|
Media Features are hosted either by their respective providers or
|
||||||
|
directly on our Services. Your interactions with these Social Media
|
||||||
|
Features are governed by the privacy policy of their respective
|
||||||
|
providers.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Email marketing</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We offer electronic newsletters to which you may voluntarily
|
||||||
|
subscribe at any time. We are committed to keeping your e-mail
|
||||||
|
address confidential and will not disclose your email address to any
|
||||||
|
third parties except as allowed in the information use and
|
||||||
|
processing section or for the purposes of utilizing a third-party
|
||||||
|
provider to send such emails. We will maintain the information sent
|
||||||
|
via e-mail in accordance with applicable laws and regulations.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In compliance with the CAN-SPAM Act, all e-mails sent from us will
|
||||||
|
clearly state who the e-mail is from and provide clear information
|
||||||
|
on how to contact the sender. You may choose to stop receiving our
|
||||||
|
newsletter or marketing emails by following the unsubscribe
|
||||||
|
instructions included in these emails or by contacting us. However,
|
||||||
|
you will continue to receive essential transactional emails.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Affiliate links</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We may engage in affiliate marketing and have affiliate links
|
||||||
|
present on the Services for the purpose of being able to offer you
|
||||||
|
related or additional products and services. If you click on an
|
||||||
|
affiliate link, a cookie will be placed on your browser to track any
|
||||||
|
sales for purposes of commissions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Links to other resources</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
The Services contain links to other resources that are not owned or
|
||||||
|
controlled by us. Please be aware that we are not responsible for
|
||||||
|
the privacy practices of such other resources or third parties. We
|
||||||
|
encourage you to be aware when you leave the Services and to read
|
||||||
|
the privacy statements of each and every resource that may collect
|
||||||
|
Personal Information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Information security</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We secure information you provide on computer servers in a
|
||||||
|
controlled, secure environment, protected from unauthorized access,
|
||||||
|
use, or disclosure. We maintain reasonable administrative,
|
||||||
|
technical, and physical safeguards in an effort to protect against
|
||||||
|
unauthorized access, use, modification, and disclosure of Personal
|
||||||
|
Information in our control and custody. However, no data
|
||||||
|
transmission over the Internet or wireless network can be
|
||||||
|
guaranteed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Therefore, while we strive to protect your Personal Information, you
|
||||||
|
acknowledge that (i) there are security and privacy limitations of
|
||||||
|
the Internet which are beyond our control; (ii) the security,
|
||||||
|
integrity, and privacy of any and all information and data exchanged
|
||||||
|
between you and the Services cannot be guaranteed; and (iii) any
|
||||||
|
such information and data may be viewed or tampered with in transit
|
||||||
|
by a third party, despite best efforts.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
As the security of Personal Information depends in part on the
|
||||||
|
security of the device you use to communicate with us and the
|
||||||
|
security you use to protect your credentials, please take
|
||||||
|
appropriate measures to protect this information.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Data breach</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
In the event we become aware that the security of the Services has
|
||||||
|
been compromised or Users’ Personal Information has been disclosed
|
||||||
|
to unrelated third parties as a result of external activity,
|
||||||
|
including, but not limited to, security attacks or fraud, we reserve
|
||||||
|
the right to take reasonably appropriate measures, including, but
|
||||||
|
not limited to, investigation and reporting, as well as notification
|
||||||
|
to and cooperation with law enforcement authorities. In the event of
|
||||||
|
a data breach, we will make reasonable efforts to notify affected
|
||||||
|
individuals if we believe that there is a reasonable risk of harm to
|
||||||
|
the User as a result of the breach or if notice is otherwise
|
||||||
|
required by law. When we do, we will send you an email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Changes and amendments</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
We reserve the right to modify this Policy or its terms related to
|
||||||
|
the Services at any time at our discretion. When we do, we will
|
||||||
|
revise the updated date at the bottom of this page. We may also
|
||||||
|
provide notice to you in other ways at our discretion, such as
|
||||||
|
through the contact information you have provided.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
An updated version of this Policy will be effective immediately upon
|
||||||
|
the posting of the revised Policy unless otherwise specified. Your
|
||||||
|
continued use of the Services after the effective date of the
|
||||||
|
revised Policy (or such other act specified at that time) will
|
||||||
|
constitute your consent to those changes. However, we will not,
|
||||||
|
without your consent, use your Personal Information in a manner
|
||||||
|
materially different than what was stated at the time your Personal
|
||||||
|
Information was collected.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Acceptance of this policy</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
You acknowledge that you have read this Policy and agree to all its
|
||||||
|
terms and conditions. By accessing and using the Services and
|
||||||
|
submitting your information you agree to be bound by this Policy. If
|
||||||
|
you do not agree to abide by the terms of this Policy, you are not
|
||||||
|
authorized to access or use the Services.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2>Contacting us</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
If you have any questions, concerns, or complaints regarding this
|
||||||
|
Policy, the information we hold about you, or if you wish to
|
||||||
|
exercise your rights, we encourage you to contact us using the
|
||||||
|
details below:
|
||||||
|
</p>
|
||||||
|
<p>info@gdevs.io</p>
|
||||||
|
<p>
|
||||||
|
We will attempt to resolve complaints and disputes and make every
|
||||||
|
reasonable effort to honor your wish to exercise your rights as
|
||||||
|
quickly as possible and in any event, within the timescales provided
|
||||||
|
by applicable data protection laws.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>This document was last updated on August 16, 2021</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(PrivacyPolicy);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const RouteBackground = () => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
css={`
|
||||||
|
position: absolute;
|
||||||
|
background: ${props => props.theme.palette.secondary.main};
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RouteBackground;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Route } from 'react-router';
|
||||||
|
|
||||||
|
function RouteWithSubRoutes(route) {
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
// eslint-disable-next-line react/destructuring-assignment
|
||||||
|
path={route.path}
|
||||||
|
render={props => (
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
<route.component {...props} routes={route.routes} />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouteWithSubRoutes;
|
||||||