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;
|
||||