├── bun.lockb
├── resources
├── icon.ico
├── installerHeader.bmp
├── installerSidebar.bmp
├── uninstallerHeader.bmp
└── uninstallerSidebar.bmp
├── web
├── assets
│ ├── vk.png
│ ├── elyby.webp
│ ├── github.png
│ ├── icon.png
│ ├── icon_2.png
│ ├── icon_3.png
│ ├── icon_4.png
│ ├── idle1.wav
│ ├── modIcon.webp
│ ├── telegram.png
│ ├── microsoft.webp
│ ├── fonts
│ │ ├── Manrope.ttf
│ │ ├── FiraCode.ttf
│ │ └── MaterialSymbols
│ │ │ ├── font.woff2
│ │ │ └── font.css
│ ├── posters
│ │ ├── 1.11.webp
│ │ ├── 1.12.webp
│ │ ├── 1.13.webp
│ │ ├── 1.14.webp
│ │ ├── 1.15.webp
│ │ ├── 1.16.webp
│ │ ├── 1.17.webp
│ │ ├── 1.18.webp
│ │ ├── 1.19.webp
│ │ ├── 1.20.webp
│ │ └── 1.21.webp
│ ├── sounds
│ │ ├── start.mp3
│ │ └── sweep.mp3
│ ├── backgrounds
│ │ ├── 1.webp
│ │ ├── 2.webp
│ │ ├── 3.webp
│ │ ├── 4.webp
│ │ ├── 5.webp
│ │ └── min
│ │ │ ├── 1.webp
│ │ │ ├── 2.webp
│ │ │ ├── 3.webp
│ │ │ ├── 4.webp
│ │ │ └── 5.webp
│ ├── versions
│ │ ├── fabric.webp
│ │ ├── forge.webp
│ │ ├── pack.webp
│ │ ├── quilt.webp
│ │ ├── 3rdparty.webp
│ │ ├── neoforge.webp
│ │ ├── vanilla.webp
│ │ └── forgeOptiFine.webp
│ ├── multiplayer_disabled.jpg
│ ├── multiplayer_enabled.jpg
│ └── flags
│ │ ├── de.svg
│ │ ├── ru.svg
│ │ ├── ua.svg
│ │ ├── en.svg
│ │ └── cn.svg
├── css
│ ├── link.css
│ ├── themes
│ │ ├── dark.css
│ │ └── light.css
│ ├── resets.css
│ ├── scrollbar.css
│ ├── input.css
│ ├── link_openInNew.css
│ ├── slider.css
│ ├── chips.css
│ ├── misc.css
│ ├── checksList.css
│ ├── checkbox.css
│ ├── colorPicker.css
│ ├── switch.css
│ ├── progress.css
│ ├── spinner.css
│ ├── tabs.css
│ ├── toast.css
│ ├── alert.css
│ ├── custom-select.css
│ ├── modal.css
│ ├── button.css
│ ├── globals.css
│ └── layout.css
├── sections
│ ├── settings
│ │ ├── startup.css
│ │ ├── personalization.css
│ │ ├── tabs.ejs
│ │ ├── about.css
│ │ ├── game.ejs
│ │ └── java.ejs
│ ├── packManager
│ │ ├── updateTab.css
│ │ ├── layoutTabs.ejs
│ │ └── tabs.ejs
│ └── packsAndMods
│ │ ├── wrappedModsList.ejs
│ │ └── filters.ejs
├── views
│ ├── startAnimation.ejs
│ ├── updateNotify.css
│ ├── updateNotify.ejs
│ ├── titleWrapped.ejs
│ ├── news.ejs
│ ├── versionsPosters.ejs
│ ├── flymenu.css
│ ├── flyout.css
│ ├── news.css
│ ├── flymenu.ejs
│ ├── versionsPosters.css
│ ├── flyout.ejs
│ └── startAnimation.css
├── modals
│ ├── createPack.css
│ ├── content.ejs
│ ├── frogLogin.css
│ ├── elybyLogin.css
│ ├── frogRegister.css
│ ├── eula.ejs
│ ├── eula.css
│ ├── eula.js
│ ├── changelog.css
│ ├── content.css
│ ├── changelog.ejs
│ ├── selectWorld.ejs
│ ├── createPack.ejs
│ ├── packs.js
│ ├── elybyLogin.ejs
│ ├── console.css
│ ├── frogLogin.ejs
│ ├── installMods.ejs
│ ├── frogRegister.ejs
│ ├── selectWorld.css
│ ├── packs.ejs
│ ├── frogSkin.css
│ ├── packs.css
│ ├── packManager.ejs
│ ├── console.ejs
│ ├── settings.ejs
│ ├── installMods.js
│ ├── changelog.js
│ ├── servers.ejs
│ ├── elybyLogin.js
│ ├── versions.css
│ ├── versions.ejs
│ ├── frogSkin.ejs
│ ├── accounts.ejs
│ ├── packManager.css
│ ├── servers.css
│ ├── accounts.css
│ └── settings.css
└── js
│ ├── FrogRequests.js
│ ├── FrogAlerts.js
│ ├── FrogConfig.js
│ ├── FrogNews.js
│ ├── FrogUpdater.js
│ ├── FrogToasts.js
│ ├── FrogPlayStats.js
│ ├── FrogErrorsParser.js
│ ├── renderer.js
│ ├── FrogServersUI.js
│ ├── colors.js
│ ├── FrogModals.js
│ ├── FrogDownloader.js
│ ├── FrogFlyout.js
│ ├── FrogElybyManager.js
│ ├── FrogAccountsUI.js
│ └── FrogCollector.js
├── .gitignore
├── windows
└── mainWindow.js
├── verifyTranslations.js
├── README.md
├── package.json
├── PRIVACY_POLICY.md
└── EULA.md
/bun.lockb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/bun.lockb
--------------------------------------------------------------------------------
/resources/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/resources/icon.ico
--------------------------------------------------------------------------------
/web/assets/vk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/vk.png
--------------------------------------------------------------------------------
/web/assets/elyby.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/elyby.webp
--------------------------------------------------------------------------------
/web/assets/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/github.png
--------------------------------------------------------------------------------
/web/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/icon.png
--------------------------------------------------------------------------------
/web/assets/icon_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/icon_2.png
--------------------------------------------------------------------------------
/web/assets/icon_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/icon_3.png
--------------------------------------------------------------------------------
/web/assets/icon_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/icon_4.png
--------------------------------------------------------------------------------
/web/assets/idle1.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/idle1.wav
--------------------------------------------------------------------------------
/web/assets/modIcon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/modIcon.webp
--------------------------------------------------------------------------------
/web/assets/telegram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/telegram.png
--------------------------------------------------------------------------------
/web/assets/microsoft.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/microsoft.webp
--------------------------------------------------------------------------------
/web/assets/fonts/Manrope.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/fonts/Manrope.ttf
--------------------------------------------------------------------------------
/web/assets/posters/1.11.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.11.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.12.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.12.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.13.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.13.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.14.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.14.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.15.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.15.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.16.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.16.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.17.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.17.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.18.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.18.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.19.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.19.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.20.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.20.webp
--------------------------------------------------------------------------------
/web/assets/posters/1.21.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/posters/1.21.webp
--------------------------------------------------------------------------------
/web/assets/sounds/start.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/sounds/start.mp3
--------------------------------------------------------------------------------
/web/assets/sounds/sweep.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/sounds/sweep.mp3
--------------------------------------------------------------------------------
/resources/installerHeader.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/resources/installerHeader.bmp
--------------------------------------------------------------------------------
/resources/installerSidebar.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/resources/installerSidebar.bmp
--------------------------------------------------------------------------------
/resources/uninstallerHeader.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/resources/uninstallerHeader.bmp
--------------------------------------------------------------------------------
/web/assets/backgrounds/1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/1.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/2.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/3.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/4.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/5.webp
--------------------------------------------------------------------------------
/web/assets/fonts/FiraCode.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/fonts/FiraCode.ttf
--------------------------------------------------------------------------------
/web/assets/versions/fabric.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/fabric.webp
--------------------------------------------------------------------------------
/web/assets/versions/forge.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/forge.webp
--------------------------------------------------------------------------------
/web/assets/versions/pack.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/pack.webp
--------------------------------------------------------------------------------
/web/assets/versions/quilt.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/quilt.webp
--------------------------------------------------------------------------------
/resources/uninstallerSidebar.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/resources/uninstallerSidebar.bmp
--------------------------------------------------------------------------------
/web/assets/backgrounds/min/1.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/min/1.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/min/2.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/min/2.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/min/3.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/min/3.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/min/4.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/min/4.webp
--------------------------------------------------------------------------------
/web/assets/backgrounds/min/5.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/backgrounds/min/5.webp
--------------------------------------------------------------------------------
/web/assets/versions/3rdparty.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/3rdparty.webp
--------------------------------------------------------------------------------
/web/assets/versions/neoforge.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/neoforge.webp
--------------------------------------------------------------------------------
/web/assets/versions/vanilla.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/vanilla.webp
--------------------------------------------------------------------------------
/web/assets/multiplayer_disabled.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/multiplayer_disabled.jpg
--------------------------------------------------------------------------------
/web/assets/multiplayer_enabled.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/multiplayer_enabled.jpg
--------------------------------------------------------------------------------
/web/css/link.css:
--------------------------------------------------------------------------------
1 | a {
2 | color: var(--theme-primary) !important;
3 | font-size: 16pt;
4 | font-weight: 600;
5 | }
--------------------------------------------------------------------------------
/web/assets/versions/forgeOptiFine.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/versions/forgeOptiFine.webp
--------------------------------------------------------------------------------
/web/assets/fonts/MaterialSymbols/font.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Seeroy/FrogLauncher2/HEAD/web/assets/fonts/MaterialSymbols/font.woff2
--------------------------------------------------------------------------------
/web/css/themes/dark.css:
--------------------------------------------------------------------------------
1 | html.dark {
2 | --bg-main: #171717;
3 | --bg-sub: #404040;
4 | --bg-transparent: rgba(23, 23, 23, 0.5);
5 | --text-color: white;
6 | --text-sub: #d1d1d1;
7 | }
--------------------------------------------------------------------------------
/web/sections/settings/startup.css:
--------------------------------------------------------------------------------
1 | #modal-settings:has(label[data-setting="fullySeparatedStorage"] input:checked) .settings-item:has(label[data-setting="separatedStorage"]) {
2 | display: none !important;
3 | }
--------------------------------------------------------------------------------
/web/assets/flags/de.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/web/assets/flags/ru.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/web/assets/flags/ua.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/web/css/themes/light.css:
--------------------------------------------------------------------------------
1 | html.light {
2 | --bg-main: #faf9f9;
3 | --bg-sub: #b8b8b8;
4 | --bg-transparent: rgba(250, 249, 249, 0.5);
5 | --text-color: #000000;
6 | --text-sub: #616161;
7 | }
8 |
9 | html.light .flymenu .item:not(.active) * {
10 | color: black;
11 | }
--------------------------------------------------------------------------------
/web/css/resets.css:
--------------------------------------------------------------------------------
1 | button, input[type="text"], select, input[type="number"], input[type="color"], input[type="password"], input[type="email"], textarea, dialog {
2 | background: none;
3 | border: none;
4 | appearance: none;
5 | outline: none;
6 | padding: 0;
7 | margin: 0;
8 | border-radius: 0;
9 | }
--------------------------------------------------------------------------------
/web/views/startAnimation.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |
FrogLauncher
7 |
{{commons.starting}}
8 |
--------------------------------------------------------------------------------
/web/modals/createPack.css:
--------------------------------------------------------------------------------
1 | /* Common stylings */
2 | #modal-createPack {
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | flex-direction: column;
7 |
8 | width: 40vw;
9 | height: max-content;
10 | gap: 8px;
11 | }
12 |
13 | #modal-createPack > * {
14 | width: 100%;
15 | }
--------------------------------------------------------------------------------
/web/css/scrollbar.css:
--------------------------------------------------------------------------------
1 | *:not(.slider)::-webkit-scrollbar-thumb {
2 | background: var(--theme-primary);
3 | border-radius: 8px;
4 | }
5 |
6 | *:not(.slider)::-webkit-scrollbar-track {
7 | background: var(--theme-primaryBgLight);
8 | border-radius: 8px;
9 | }
10 |
11 | *:not(.slider)::-webkit-scrollbar {
12 | width: 6px;
13 | }
--------------------------------------------------------------------------------
/web/modals/content.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%- include('../views/updateNotify'); %>
6 | <%- include('../views/news'); %>
7 | <%- include('../views/versionsPosters'); %>
8 |
9 |
--------------------------------------------------------------------------------
/web/modals/frogLogin.css:
--------------------------------------------------------------------------------
1 | #modal-frogLogin {
2 | padding: 18px;
3 |
4 | width: 36vw;
5 | height: max-content;
6 |
7 | background: var(--theme-primaryBg);
8 |
9 | display: flex;
10 | flex-direction: column;
11 | gap: 12px;
12 | }
13 |
14 | #modal-frogLogin button.loginBtn {
15 | border-radius: 9999px;
16 | width: 100%;
17 | }
--------------------------------------------------------------------------------
/web/modals/elybyLogin.css:
--------------------------------------------------------------------------------
1 | #modal-elybyLogin {
2 | padding: 18px;
3 |
4 | width: 36vw;
5 | height: max-content;
6 |
7 | background: var(--theme-primaryBg);
8 |
9 | display: flex;
10 | flex-direction: column;
11 | gap: 12px;
12 | }
13 |
14 | #modal-elybyLogin button.loginBtn {
15 | border-radius: 9999px;
16 | width: 100%;
17 | }
--------------------------------------------------------------------------------
/web/modals/frogRegister.css:
--------------------------------------------------------------------------------
1 | #modal-frogRegister {
2 | padding: 18px;
3 |
4 | width: 36vw;
5 | height: max-content;
6 |
7 | background: var(--theme-primaryBg);
8 |
9 | display: flex;
10 | flex-direction: column;
11 | gap: 12px;
12 | }
13 |
14 | #modal-frogRegister button.loginBtn {
15 | border-radius: 9999px;
16 | width: 100%;
17 | }
--------------------------------------------------------------------------------
/web/modals/eula.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 | .idea/
10 | pids
11 | *.pid
12 | *.seed
13 | *.pid.lock
14 | node_modules/
15 | .npm
16 | .eslintcache
17 | .stylelintcache
18 | .node_repl_history
19 | *.tgz
20 | .env
21 | .env.development.local
22 | .env.test.local
23 | .env.production.local
24 | .env.local
25 | .cache
26 | .parcel-cache
27 | out
28 | dist
29 | .cache/
--------------------------------------------------------------------------------
/web/modals/eula.css:
--------------------------------------------------------------------------------
1 | #modal-eula {
2 | max-width: 90vw;
3 | max-height: 90vh;
4 |
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | gap: 24px;
9 | }
10 |
11 | #modal-eula .data {
12 | overflow-y: auto;
13 | max-height: 70vh;
14 | }
15 |
16 | #modal-eula .buttons {
17 | display: flex;
18 | gap: 8px;
19 | height: max-content;
20 | }
21 |
22 | #modal-eula .buttons button {
23 | min-width: 20vw;
24 | }
--------------------------------------------------------------------------------
/web/css/input.css:
--------------------------------------------------------------------------------
1 | input[type="text"], input[type="password"], input[type="email"], input[type="number"] {
2 | appearance: none;
3 | border: 0;
4 | outline: none;
5 |
6 | background: var(--theme-primaryBgLight);
7 | padding: 8px;
8 | border-radius: 8px;
9 | color: var(--text-color);
10 | font-size: 14pt;
11 |
12 | min-width: 128px;
13 | max-width: 100vw;
14 | }
15 |
16 | input.lighter {
17 | background: var(--theme-primaryBgLighter);
18 | }
--------------------------------------------------------------------------------
/web/css/link_openInNew.css:
--------------------------------------------------------------------------------
1 | .link {
2 | font-size: 14pt;
3 |
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | flex-direction: row;
8 | cursor: pointer;
9 | gap: 4px;
10 | }
11 |
12 | .link * {
13 | display: inline-block !important;
14 | }
15 |
16 | .link,
17 | .link * {
18 | text-decoration: none !important;
19 | color: var(--theme-primaryLighter) !important;
20 | }
21 |
22 | .link:hover * {
23 | color: var(--theme-primary) !important;
24 | }
--------------------------------------------------------------------------------
/web/modals/eula.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | if(FrogConfig.read("eulaConfirmed", false) !== true){
3 | $("#modal-eula").show();
4 | $("#modal-eula .data").load("https://froglauncher.ru/privacy-policy");
5 | }
6 | })
7 |
8 | // При подтверждении
9 | $("#modal-eula .confirm").click(() => {
10 | $("#modal-eula").hide();
11 | FrogConfig.write("eulaConfirmed", true);
12 | })
13 |
14 | // При отклонении
15 | $("#modal-eula .decline").click(() => {
16 | FrogUI.closeMainWindow();
17 | })
--------------------------------------------------------------------------------
/web/modals/changelog.css:
--------------------------------------------------------------------------------
1 | #modal-changelog .list .item {
2 | width: 100%;
3 | height: max-content;
4 | padding: 12px;
5 | border-radius: 8px;
6 | display: flex;
7 | flex-direction: column;
8 | gap: 12px;
9 | justify-content: center;
10 | background: var(--theme-primaryBgLight);
11 | }
12 |
13 | #modal-changelog .list {
14 | display: flex;
15 | flex-direction: column;
16 | gap: 12px;
17 | height: max-content;
18 | }
19 |
20 | #modal-changelog .wrapper {
21 | overflow: auto;
22 | height: 60vh;
23 | padding: 4px 12px;
24 | }
--------------------------------------------------------------------------------
/web/modals/content.css:
--------------------------------------------------------------------------------
1 | #modal-content {
2 | max-height: 100%;
3 | background: var(--theme-primaryBg);
4 | padding: 12px;
5 | border-radius: 12px;
6 | z-index: 1;
7 | }
8 |
9 | #modal-content .scroll-wrap {
10 | overflow-y: auto;
11 | max-height: 73vh;
12 | gap: 12px;
13 | padding: 10px;
14 | }
15 |
16 | #modal-content .scroll-wrap > *:not(:first-child) {
17 | margin: 16px 0;
18 | }
19 |
20 | #modal-content::-webkit-scrollbar-track {
21 | background: transparent;
22 | }
23 |
24 | #modal-content::-webkit-scrollbar {
25 | width: 8px;
26 | }
--------------------------------------------------------------------------------
/web/views/updateNotify.css:
--------------------------------------------------------------------------------
1 | #updateNotify {
2 | width: 100%;
3 | border-radius: 10px;
4 | gap: 12px;
5 | }
6 |
7 | #updateNotify .caption {
8 | font-size: 15pt;
9 | font-weight: 700;
10 | }
11 |
12 | #updateNotify .description {
13 | font-size: 13pt;
14 | font-weight: 500;
15 | color: var(--text-sub);
16 | }
17 |
18 | #updateNotify button {
19 | font-size: 14pt;
20 | padding: 8px 21px !important;
21 | border-radius: 32px !important;
22 | height: max-content !important;
23 | }
24 |
25 | #updateNotify .link * {
26 | font-size: 13pt;
27 | }
--------------------------------------------------------------------------------
/web/modals/changelog.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 | history
9 |
{{changelog.title}}
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/modals/selectWorld.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
{{selectWorld.title}}
9 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/assets/fonts/MaterialSymbols/font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Material Symbols Outlined';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: url("font.woff2") format('woff2');
6 | }
7 |
8 | .material-symbols-outlined {
9 | font-family: 'Material Symbols Outlined';
10 | font-weight: normal;
11 | font-style: normal;
12 | font-size: 24px;
13 | line-height: 1;
14 | letter-spacing: normal;
15 | text-transform: none;
16 | display: inline-block;
17 | white-space: nowrap;
18 | word-wrap: normal;
19 | direction: ltr;
20 | -webkit-font-feature-settings: 'liga';
21 | -webkit-font-smoothing: antialiased;
22 | }
--------------------------------------------------------------------------------
/web/assets/flags/en.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/web/css/slider.css:
--------------------------------------------------------------------------------
1 | input[type="range"] {
2 | -webkit-appearance: none;
3 | width: 100%;
4 | height: 32px;
5 | background: transparent;
6 | outline: none;
7 | -webkit-transition: .2s;
8 | transition: opacity .2s;
9 | }
10 |
11 | input[type="range"]::-webkit-slider-runnable-track {
12 | height: 7px !important;
13 | background: var(--theme-primaryBgLighter);
14 | border-radius: 16px;
15 | }
16 |
17 | input[type="range"]::-webkit-slider-thumb {
18 | -webkit-appearance: none;
19 | appearance: none;
20 | width: 21px;
21 | height: 21px;
22 | margin-top: -6px;
23 | background: var(--theme-primary);
24 | border-radius: 9999px;
25 | cursor: pointer;
26 | }
--------------------------------------------------------------------------------
/web/modals/createPack.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | new_window
6 |
{{packs.newPack.title}}
7 |
10 |
11 |
12 |
13 |
{{packs.newPack.description}}
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web/assets/flags/cn.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/web/sections/settings/personalization.css:
--------------------------------------------------------------------------------
1 | #languages-list {
2 | display: grid;
3 | grid-template-columns: 1fr 1fr;
4 | grid-gap: 0.5em;
5 | }
6 |
7 | #languages-list .item .icon {
8 | width: 28px;
9 | height: 28px;
10 | border-radius: 9999px;
11 | }
12 |
13 | #languages-list .item .text > span:first-child {
14 | font-weight: 600;
15 | }
16 |
17 | #languages-list .item .text > span:last-child,
18 | #languages-list .item .text sup {
19 | color: var(--text-sub);
20 | }
21 |
22 | #languages-list .item .text sup {
23 | opacity: 0.88;
24 | font-weight: 400 !important;
25 | }
26 |
27 | #modal-settings .settings-item .wallpapers-list-wrapper {
28 | display: flex;
29 | flex-wrap: wrap;
30 | gap: 10px;
31 | justify-content: end;
32 | align-items: center;
33 | max-width: 50%;
34 | }
--------------------------------------------------------------------------------
/web/views/updateNotify.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
update
5 |
6 | {{updater.available}}
7 | {{updater.downloading}}
8 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/web/modals/packs.js:
--------------------------------------------------------------------------------
1 | $(function () {
2 | $(document).on("showModalEvent", (e) => {
3 | if (e.originalEvent.detail.modal === "packs") {
4 | FrogPacksUI.refreshDirectorySelect();
5 | }
6 | })
7 |
8 | $("#modal-packs .item.add button.install").click(function () {
9 | FrogPacksUI.setCurrentMode("modpacks");
10 | FrogPacksUI.reloadAll(true, true, true);
11 | FrogModals.hideModal("packManager");
12 | FrogModals.switchModal("installMods");
13 | })
14 |
15 | $("#modal-packs .item.add button.create").click(function () {
16 | FrogPacksUI.setCurrentMode("modpacks");
17 | FrogPacksUI.reloadAll(true, true, true);
18 | FrogModals.showModal("createPack");
19 | })
20 |
21 | $("#modal-packs .item.add button.import").click(function () {
22 | FrogPacks.importModpack();
23 | })
24 | })
--------------------------------------------------------------------------------
/windows/mainWindow.js:
--------------------------------------------------------------------------------
1 | let path = require("path");
2 | const {BrowserWindow} = require("electron");
3 | const ejs = require('ejs-electron');
4 |
5 | const MW_OPTIONS = {
6 | width: 1400,
7 | height: 900,
8 | minWidth: 1150,
9 | minHeight: 750,
10 | show: false,
11 | icon: path.join(__dirname, "../web/assets/icon.png"),
12 | resizable: true,
13 | maximizable: true,
14 | autoHideMenuBar: true,
15 | frame: false,
16 | transparent: true,
17 | webPreferences: {
18 | nodeIntegration: true,
19 | contextIsolation: false,
20 | },
21 | };
22 |
23 | exports.create = (cb) => {
24 | let winObj = new BrowserWindow(MW_OPTIONS);
25 |
26 | ejs.listen();
27 | winObj.loadURL("file://" + __dirname + "/../web/index.ejs");
28 |
29 | winObj.once("ready-to-show", () => {
30 | winObj.show();
31 | cb(winObj);
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/web/css/chips.css:
--------------------------------------------------------------------------------
1 | .chip {
2 | background: var(--theme-primaryBgLighter);
3 | width: max-content;
4 | height: max-content;
5 |
6 | padding: 4px 10px;
7 | border-radius: 16px;
8 |
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | gap: 6px;
13 | }
14 |
15 | .chip.small {
16 | font-size: 10pt;
17 | padding: 4px 8px;
18 | }
19 |
20 | .chip.small * {
21 | font-size: 10pt;
22 | }
23 |
24 | .chip.large {
25 | font-size: 17pt;
26 | padding: 6px 16px;
27 | gap: 8px;
28 | }
29 |
30 | .chip.pill {
31 | border-radius: 9999px;
32 | }
33 |
34 | .chip.square {
35 | border-radius: 6px;
36 | }
37 |
38 | .chip.bgLight {
39 | background: var(--theme-primaryBgLight);
40 | }
41 |
42 | .chip.primary {
43 | background: var(--theme-primary);
44 | }
45 |
46 | .chip.primaryDarker {
47 | background: var(--theme-primaryDarker);
48 | }
--------------------------------------------------------------------------------
/web/modals/elybyLogin.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
{{elyby.title}}
9 |
10 |
11 |
12 |
13 |
{{elyby.totpNotify}}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/web/modals/console.css:
--------------------------------------------------------------------------------
1 | #modal-console {
2 | padding-left: 7vw;
3 | padding-right: 7vw;
4 | }
5 |
6 | #modal-console .wrapper {
7 | background: var(--theme-primaryBgLight);
8 | overflow: auto !important;
9 | width: max-content;
10 | height: 82%;
11 | padding: 8px;
12 | margin: 16px 0;
13 | border-radius: 8px;
14 | }
15 |
16 | #modal-console .wrapper .console-textarea {
17 | width: 66vw;
18 | height: max-content;
19 | min-height: 100%;
20 | user-select: text;
21 | font-family: "Fira Code";
22 | font-size: 11pt;
23 | }
24 |
25 | #modal-console .wrapper::-webkit-scrollbar {
26 | width: 4px;
27 | }
28 |
29 | #modal-console button {
30 | font-size: 14pt;
31 | padding: 6px 12px !important;
32 | height: max-content;
33 | width: max-content;
34 | border-radius: 8px !important;
35 | }
36 |
37 | #modal-console button:last-child {
38 | background: var(--success);
39 | color: white !important;
40 | }
--------------------------------------------------------------------------------
/web/js/FrogRequests.js:
--------------------------------------------------------------------------------
1 | class FrogRequests {
2 | static get = (url) => {
3 | return new Promise(resolve => {
4 | $.get(url, (response) => {
5 | resolve([true, response]);
6 | }).fail(e => {
7 | resolve([false, e]);
8 | })
9 | })
10 | }
11 |
12 | static post = (url, data = {}, contentType = false, processData = false) => {
13 | return new Promise(resolve => {
14 | $.ajax({
15 | url: url,
16 | type: "POST",
17 | data: data,
18 | success: (response) => {
19 | resolve([true, response]);
20 | },
21 | error: (e) => {
22 | console.error(e);
23 | resolve([false, e]);
24 | },
25 | processData: processData,
26 | contentType: contentType
27 | });
28 | })
29 | }
30 | }
--------------------------------------------------------------------------------
/web/modals/frogLogin.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
{{frogAuth.login.title}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | open_in_new
19 | {{frogAuth.goRegister}}
20 |
21 |
--------------------------------------------------------------------------------
/web/views/titleWrapped.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
FrogLauncher
6 |
7 |
8 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/web/views/news.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | newsmode
5 | {{commons.news}}
6 |
7 |
8 |
9 |
10 |
11 |
{{loading.progress}}
12 |
13 |
14 |
15 |
$1
16 |
$2
17 |
$3
18 |
19 | open_in_new
20 | {{commons.open}}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/web/css/misc.css:
--------------------------------------------------------------------------------
1 | .flex {
2 | display: flex;
3 | }
4 |
5 | .flex-center {
6 | align-items: center;
7 | justify-content: center;
8 | }
9 |
10 | .flex-align-center {
11 | align-items: center;
12 | }
13 |
14 | .flex-justify-center {
15 | justify-content: center;
16 | }
17 |
18 | .flex-col {
19 | flex-direction: column;
20 | }
21 |
22 | .flex-wrap {
23 | flex-wrap: wrap;
24 | }
25 |
26 | .flex-grow {
27 | flex-grow: 1;
28 | }
29 |
30 | .flex-row {
31 | flex-direction: row;
32 | }
33 |
34 | .flex-gap-8 {
35 | gap: 8px
36 | }
37 |
38 | .flex-gap-4 {
39 | gap: 4px
40 | }
41 |
42 | .flex-gap-v-8 {
43 | gap: 0 8px;
44 | }
45 |
46 | .flex-gap-h-8 {
47 | gap: 0 8px;
48 | }
49 |
50 | /* Microdot */
51 | .microdot {
52 | display: flex;
53 | align-items: center;
54 | justify-content: center;
55 | width: 10px;
56 | height: 10px;
57 | }
58 |
59 | .microdot .dot {
60 | width: 4px;
61 | height: 4px;
62 | border-radius: 9999px;
63 | background: var(--text-sub);
64 | opacity: 0.6;
65 | }
--------------------------------------------------------------------------------
/web/modals/installMods.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{packs.installTo}}
7 |
8 |
11 |
12 |
13 |
14 | <%- include('../sections/packsAndMods/filters.ejs'); %>
15 |
16 |
17 |
18 |
19 |
20 | <%- include('../sections/packsAndMods/wrappedModsList.ejs'); %>
21 |
22 |
23 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/web/css/checksList.css:
--------------------------------------------------------------------------------
1 | .checksList {
2 | display: flex;
3 | padding: 8px;
4 | flex-direction: column;
5 | overflow-y: auto;
6 | overflow-x: hidden;
7 | min-width: 320px;
8 | }
9 |
10 | .checksList .item {
11 | display: flex;
12 | border-radius: 8px;
13 | background: var(--theme-primaryBg);
14 | border: 3px solid var(--theme-primaryBgLighter);
15 | padding: 14px 16px;
16 | align-items: center;
17 | margin: 4px 0;
18 | cursor: pointer;
19 | }
20 |
21 | .checksList .item:not(.active) * {
22 | color: var(--text-color);
23 | }
24 |
25 | .checksList .item:not(.active):hover {
26 | filter: brightness(120%);
27 | }
28 |
29 | .checksList .item:not(.active) .check {
30 | display: none !important;
31 | }
32 |
33 | .checksList .item.active {
34 | border-color: var(--theme-primary);
35 | }
36 |
37 | .checksList .item.active .material-symbols-outlined {
38 | color: var(--theme-primary);
39 | }
40 |
41 | .checksList .item .text {
42 | flex-grow: 1;
43 | }
44 |
45 | .checksList .item .check {
46 | font-size: 16px;
47 | }
--------------------------------------------------------------------------------
/web/modals/frogRegister.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
{{frogAuth.register.title}}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | open_in_new
20 | {{frogAuth.goLogin}}
21 |
22 |
--------------------------------------------------------------------------------
/web/css/checkbox.css:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | position: absolute;
3 | z-index: -1;
4 | opacity: 0;
5 | margin-bottom: 1em;
6 | }
7 |
8 | .checkbox + label {
9 | display: inline-flex;
10 | align-items: center;
11 | user-select: none;
12 | cursor: pointer;
13 | }
14 |
15 | .checkbox + label::before {
16 | content: '';
17 | display: inline-block;
18 | width: 22px;
19 | height: 22px;
20 | flex-shrink: 0;
21 | flex-grow: 0;
22 | border: 1px solid #adb5bd;
23 | border-radius: 0.25em;
24 | margin-right: 0.5em;
25 | background-repeat: no-repeat;
26 | background-position: center center;
27 | background-size: 50% 50%;
28 | }
29 |
30 | .checkbox:checked + label::before {
31 | border-color: var(--theme-primary);
32 | background-color: var(--theme-primary);
33 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
34 | }
35 |
36 | .checkbox:disabled + label::before {
37 | background-color: var(--theme-primaryDarker);
38 | }
--------------------------------------------------------------------------------
/web/css/colorPicker.css:
--------------------------------------------------------------------------------
1 | .color-picker {
2 | width: 32px;
3 | height: 32px;
4 | border-radius: 8px;
5 |
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | cursor: pointer;
10 | }
11 |
12 | input.color-picker {
13 | opacity: 0;
14 | pointer-events: none;
15 | position: absolute;
16 | }
17 |
18 | .color-edit {
19 | background: var(--theme-primaryBgLighter);
20 | padding: 0;
21 | margin: 0;
22 | width: 38px;
23 | height: 38px;
24 | border-radius: 8px;
25 | min-width: 32px;
26 | }
27 |
28 | .color-edit:hover {
29 | filter: brightness(118%);
30 | background: var(--theme-primaryBgLighter);
31 | }
32 |
33 | .color-edit * {
34 | color: var(--text-color);
35 | font-size: 10pt;
36 | }
37 |
38 | .color-picker:hover {
39 | filter: brightness(86%);
40 | }
41 |
42 | .color-picker .material-symbols-outlined {
43 | color: white;
44 | font-size: 16px;
45 | }
46 |
47 | .color-picker:not(.active) .material-symbols-outlined {
48 | display: none;
49 | }
50 |
51 | .color-picker.active .material-symbols-outlined {
52 | display: initial;
53 | }
--------------------------------------------------------------------------------
/web/modals/selectWorld.css:
--------------------------------------------------------------------------------
1 | #modal-selectWorld {
2 | width: max-content;
3 | min-width: 32vw;
4 | height: max-content;
5 | background: var(--theme-primaryBg);
6 | }
7 |
8 | #modal-selectWorld .worlds-list-wrapper {
9 | background: var(--theme-primaryBgDark);
10 | padding: 8px;
11 | overflow-y: auto;
12 | max-height: 50vh;
13 | border-radius: 8px;
14 | }
15 |
16 | #modal-selectWorld .worlds-list {
17 | display: flex;
18 | flex-direction: column;
19 | align-items: end;
20 | width: 100%;
21 | height: max-content;
22 | gap: 8px;
23 | }
24 |
25 | #modal-selectWorld .worlds-list .item {
26 | width: 100%;
27 | height: max-content;
28 | background: var(--theme-primaryBg);
29 | padding: 12px;
30 | border-radius: 8px;
31 | transition: 0s;
32 | }
33 |
34 | #modal-selectWorld .worlds-list .item.active {
35 | border: 2px solid var(--theme-primary);
36 | }
37 |
38 | #modal-selectWorld .worlds-list .item.sub {
39 | padding-left: 32px;
40 | width: 90%;
41 | cursor: pointer;
42 | }
43 |
44 | #modal-selectWorld .select {
45 | width: 100%;
46 | margin-top: 10px;
47 | }
--------------------------------------------------------------------------------
/web/modals/packs.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | package_2
6 |
{{flymenu.packs}}
7 |
8 |
9 |
10 |
11 |
15 |
18 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/web/sections/settings/tabs.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | format_paint
5 |
6 |
{{settings.sections.personalization}}
7 |
8 |
9 |
10 |
11 | sports_esports
12 |
13 |
{{settings.sections.game}}
14 |
15 |
16 |
17 |
18 | restart_alt
19 |
20 |
{{settings.sections.startup}}
21 |
22 |
23 |
24 |
25 | web_asset
26 |
27 |
{{settings.sections.java}}
28 |
29 |
30 |
31 |
32 | info
33 |
34 |
{{settings.sections.about}}
35 |
36 |
--------------------------------------------------------------------------------
/web/sections/packManager/updateTab.css:
--------------------------------------------------------------------------------
1 | #modal-packManager .updateTab,
2 | #modal-packManager .updateTab .updateMode,
3 | #modal-packManager .updateTab .foundUpdateMode {
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | }
8 |
9 | #modal-packManager .updateTab > div,
10 | #modal-packManager .updateTab .scroll-wrap {
11 | width: 100%;
12 | }
13 |
14 | #modal-packManager .updateTab .scroll-wrap {
15 | max-height: 47vh;
16 | }
17 |
18 | #modal-packManager .updateTab .updateMode,
19 | #modal-packManager .updateTab .foundUpdateMode {
20 | flex-direction: column;
21 | gap: 12px;
22 | }
23 |
24 | /* Updates list mode */
25 | #modal-packManager .updateTab .foundUpdateMode .updatesListWrapper {
26 | width: 100%;
27 | overflow: auto;
28 | }
29 |
30 | #modal-packManager .updateTab .foundUpdateMode .updatesList {
31 | display: flex;
32 | flex-direction: column;
33 | gap: 6px;
34 | width: 100%;
35 | height: max-content;
36 | }
37 |
38 | #modal-packManager .updateTab .foundUpdateMode .updatesList .item {
39 | display: flex;
40 | flex-direction: column;
41 | justify-content: center;
42 |
43 | gap: 4px;
44 | border-radius: 8px;
45 |
46 | width: 100%;
47 | height: max-content;
48 | padding: 12px;
49 |
50 | background: var(--theme-primaryBgLight);
51 | }
--------------------------------------------------------------------------------
/web/css/switch.css:
--------------------------------------------------------------------------------
1 | .switch {
2 | position: relative;
3 | display: inline-block;
4 | width: 64px;
5 | height: 34px;
6 | }
7 |
8 | .switch input {
9 | opacity: 0;
10 | width: 0;
11 | height: 0;
12 | }
13 |
14 | .switch .inner {
15 | cursor: pointer;
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | right: 0;
20 | bottom: 0;
21 | background-image: linear-gradient(270deg, var(--theme-primaryBgLighter) 0%, var(--theme-primaryBgLight) 50%, var(--theme-primary) 50%, var(--theme-primaryDarker) 100%);
22 | background-size: 132px;
23 | background-position: -67px;
24 | -webkit-transition: .4s;
25 | transition: .4s;
26 | border-radius: 34px;
27 | }
28 |
29 | .switch .inner:before {
30 | position: absolute;
31 | content: "";
32 | height: 26px;
33 | width: 26px;
34 | left: 3px;
35 | bottom: 4px;
36 | background-color: var(--theme-text);
37 | -webkit-transition: .4s;
38 | transition: .4s;
39 | border-radius: 50%;
40 | }
41 |
42 | input:checked + .inner {
43 | background-position: 0;
44 | }
45 |
46 | input:focus + .inner {
47 | box-shadow: 0 0 1px var(--theme-primary);
48 | }
49 |
50 | input:checked + .inner:before {
51 | -webkit-transform: translateX(32px);
52 | -ms-transform: translateX(32px);
53 | transform: translateX(32px);
54 | }
--------------------------------------------------------------------------------
/web/js/FrogAlerts.js:
--------------------------------------------------------------------------------
1 | const FROG_ALERT_PLACEHOLDER =
2 | '';
3 |
4 | class FrogAlerts {
5 | static create(caption, text, buttonText, icon, cb = () => {
6 | }, additionalElements = "") {
7 | icon = `${icon}`;
8 | let randomID = "alert-" + Math.floor(Math.random() * (1000 - 10 + 1)) + 10;
9 | $("body").append(
10 | FROG_ALERT_PLACEHOLDER.replaceAll(/\$1/gim, caption)
11 | .replaceAll(/\$2/gim, text)
12 | .replaceAll(/\$3/gim, buttonText)
13 | .replaceAll(/\$4/gim, icon)
14 | .replaceAll(/\$5/gim, randomID)
15 | .replaceAll(/\$6/gim, additionalElements)
16 | );
17 | animateCSSNode($("#" + randomID)[0], "fadeIn", true);
18 | $("#cmbtn-" + randomID)
19 | .click(function () {
20 | animateCSSNode($("#" + randomID)[0], "fadeOut", true).then(() => {
21 | $(this).parent().parent().remove();
22 | });
23 | cb();
24 | });
25 | }
26 | }
--------------------------------------------------------------------------------
/web/js/FrogConfig.js:
--------------------------------------------------------------------------------
1 | let mainConfig;
2 | let defaultConfig = {
3 | accounts: [],
4 | theme: {
5 | mode: 'dark',
6 | color: "rgb(10, 115, 255)"
7 | }
8 | };
9 |
10 | class FrogConfig {
11 | // Прочитать конфиг-файл
12 | static readConfig() {
13 | if (fs.existsSync(CONFIG_PATH)) {
14 | return JSON.parse(fs.readFileSync(CONFIG_PATH));
15 | } else {
16 | this.writeConfig(defaultConfig);
17 | return defaultConfig;
18 | }
19 | }
20 |
21 | // Записать конфиг-файл
22 | static writeConfig(config) {
23 | fs.writeFileSync(
24 | CONFIG_PATH,
25 | JSON.stringify(config, null, "\t")
26 | );
27 | return true;
28 | }
29 |
30 | // Прочитать переменную из конфига
31 | static read = (key, defaultValue = true) => {
32 | if (!this.isKeyExists(key)) {
33 | this.write(key, defaultValue);
34 | return defaultValue;
35 | }
36 | return mainConfig[key];
37 | };
38 |
39 | // Записать переменную в конфиг
40 | static write = (key, value) => {
41 | mainConfig[key] = value;
42 | return this.writeConfig(mainConfig);
43 | }
44 |
45 | // Существует ли ключ в конфиге
46 | static isKeyExists = (key) => {
47 | if (typeof mainConfig === "undefined") {
48 | return false;
49 | }
50 | return typeof mainConfig[key] !== "undefined";
51 | }
52 | }
53 |
54 | mainConfig = FrogConfig.readConfig();
--------------------------------------------------------------------------------
/web/modals/frogSkin.css:
--------------------------------------------------------------------------------
1 | #modal-frogSkin {
2 | height: 65vh;
3 | width: 65vw;
4 | }
5 |
6 | #modal-frogSkin .layout {
7 | display: grid;
8 | width: 100%;
9 | height: 75%;
10 |
11 | grid-template-columns: 1.2fr 1fr 1.2fr;
12 | grid-template-rows: 1fr;
13 | gap: 2px;
14 | }
15 |
16 | #modal-frogSkin .error {
17 | width: 100%;
18 | color: var(--error);
19 | text-align: center;
20 | }
21 |
22 | #modal-frogSkin .layout > div {
23 | display: flex;
24 | flex-direction: column;
25 | gap: 6px;
26 | align-items: center;
27 | justify-content: center;
28 | }
29 |
30 | #modal-frogSkin .layout .dragzone {
31 | width: 60%;
32 | height: 80%;
33 |
34 | border: 4px dashed var(--theme-primaryBgLight);
35 | cursor: pointer;
36 |
37 | display: flex;
38 | flex-direction: column;
39 | align-items: center;
40 | justify-content: center;
41 | padding: 16px;
42 | }
43 |
44 | #modal-frogSkin .layout .dragzone.dragged h4 {
45 | display: none;
46 | }
47 |
48 | #modal-frogSkin .layout .dragzone h4 {
49 | text-align: center;
50 | }
51 |
52 | #modal-frogSkin .layout .dragzone:hover,
53 | #modal-frogSkin .layout .dragzone.dragged {
54 | border: 4px dashed var(--theme-primaryBgLighter);
55 | background: var(--theme-primaryBgLight);
56 | }
57 |
58 | #skin_container {
59 | align-self: center;
60 | justify-self: center;
61 | align-content: center;
62 | justify-content: center;
63 | pointer-events: none;
64 |
65 | width: 300% !important;
66 | height: 100% !important;
67 | }
--------------------------------------------------------------------------------
/web/css/progress.css:
--------------------------------------------------------------------------------
1 | .progress-pill {
2 | background: var(--theme-primaryBgLight);
3 | border-radius: 8px;
4 | }
5 |
6 | .progress-pill, .progress-pill * {
7 | transition: 0s !important;
8 | }
9 |
10 | /* Внутренняя часть */
11 | .progress-pill .inner {
12 | background: var(--theme-primary);
13 | overflow: hidden;
14 | height: 100%;
15 | }
16 |
17 | .progress-pill .inner.indeterminate {
18 | animation: indeterminateAnimation 2s infinite linear;
19 | transform-origin: 0 50%;
20 | width: 100% !important;
21 | }
22 |
23 | /* Текст и враппер */
24 | .progress-pill .title,
25 | .progress-pill .description,
26 | .progress-pill .percent {
27 | color: var(--theme-text);
28 | }
29 |
30 | .progress-pill .title {
31 | font-size: 16pt;
32 | font-weight: 600;
33 | }
34 |
35 | .progress-pill .description {
36 | font-size: 11pt;
37 | }
38 |
39 | .progress-pill .wrapper {
40 | display: flex;
41 | flex-direction: column;
42 | align-items: start;
43 | justify-content: center;
44 | position: relative;
45 | top: -54px;
46 | left: 16px;
47 | height: 89%;
48 | }
49 |
50 | .progress-pill .percent {
51 | position: relative;
52 | font-size: 18pt;
53 | font-weight: 800;
54 | top: -50px;
55 | right: 16px;
56 | }
57 |
58 | /* Анимация */
59 | @keyframes indeterminateAnimation {
60 | 0% {
61 | transform: translateX(0) scaleX(0);
62 | }
63 | 40% {
64 | transform: translateX(0) scaleX(0.4);
65 | }
66 | 100% {
67 | transform: translateX(100%) scaleX(0.5);
68 | }
69 | }
--------------------------------------------------------------------------------
/web/sections/settings/about.css:
--------------------------------------------------------------------------------
1 | /* Clickable */
2 | .settings-item.clickable:hover {
3 | cursor: pointer;
4 | filter: brightness(119%);
5 | }
6 |
7 | #aboutItem {
8 | background-image: linear-gradient(90deg, var(--theme-primaryBgLight) 0%, var(--theme-primaryBgLighter) 50%, var(--theme-primaryBgLight) 100%) !important;
9 | background-position: 0 0;
10 | animation: gradAnim 5s linear infinite normal;
11 | }
12 |
13 | /* Описание */
14 | #aboutItem .description {
15 | display: flex;
16 | gap: 2px;
17 | align-items: center;
18 | max-width: 100vw !important;
19 | }
20 |
21 | #aboutItem .description h3 {
22 | white-space: nowrap;
23 | text-overflow: ellipsis;
24 | max-width: 13vw;
25 | }
26 |
27 | #aboutItem .description,
28 | #aboutItem .description * {
29 | font-weight: 500;
30 | }
31 |
32 | #aboutItem * {
33 | color: var(--text-color) !important;
34 | }
35 |
36 | /* Разные текста */
37 | #about_operatingSystem,
38 | #about_username {
39 | color: var(--theme-primaryBgLighter);
40 | }
41 |
42 | /* Заголовок */
43 | #aboutItem .name {
44 | font-size: 16pt !important;
45 | font-weight: 700 !important;
46 | }
47 |
48 | #modal-settings .grid-wrapper {
49 | display: grid;
50 | grid-template-columns: 1fr 1fr;
51 | gap: 0.4em;
52 | width: 100%;
53 | height: 100%;
54 | }
55 |
56 | @keyframes gradAnim {
57 | 0% {
58 | background-position: 0;
59 | }
60 |
61 | 50% {
62 | background-position: 30vw;
63 | }
64 |
65 | 100% {
66 | background-position: 62vw;
67 | }
68 | }
--------------------------------------------------------------------------------
/web/sections/packManager/layoutTabs.ejs:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
18 |
19 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
{{packs.updatePackM.checking}}
35 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/web/modals/packs.css:
--------------------------------------------------------------------------------
1 | #modal-packs .wrapper .grid {
2 | margin-top: 8px;
3 | display: grid;
4 | grid-template-columns: repeat(auto-fill, minmax(100px, 256px));
5 | gap: 12px;
6 | justify-content: center;
7 | }
8 |
9 | #modal-packs > div:nth-child(1) {
10 | margin-bottom: 0;
11 | }
12 |
13 | #modal-packs .wrapper {
14 | padding: 16px 0;
15 | }
16 |
17 | #modal-packs .wrapper .grid .item {
18 | width: 100%;
19 | height: 100%;
20 |
21 | display: flex;
22 | flex-direction: column;
23 | gap: 8px;
24 | background: var(--theme-primaryBgLight);
25 | padding: 16px;
26 | border-radius: 10px;
27 | cursor: pointer;
28 | }
29 |
30 | #modal-packs .wrapper .grid .item.add {
31 | align-items: center;
32 | justify-content: center;
33 | cursor: default;
34 | }
35 |
36 | #modal-packs .wrapper .grid .item.add button {
37 | width: 100%;
38 | }
39 |
40 | #modal-packs .wrapper .grid .item .version {
41 | font-size: 12pt;
42 | color: var(--text-sub);
43 | }
44 |
45 | #modal-packs .wrapper .grid .item:not(.add):hover {
46 | filter: brightness(113%);
47 | }
48 |
49 | #modal-packs .wrapper .grid .item .img-wrap img {
50 | width: 96px;
51 | height: 96px;
52 | border-radius: 18px;
53 | }
54 |
55 | #modal-packs .wrapper .grid .item .img-wrap {
56 | width: 100%;
57 | height: 145px;
58 | display: flex;
59 | align-items: center;
60 | justify-content: center;
61 | background: var(--theme-primaryBgLighter);
62 | border-radius: 16px;
63 | }
64 |
65 | #modal-packs .wrapper {
66 | height: 98%;
67 | overflow-y: auto;
68 | }
--------------------------------------------------------------------------------
/web/modals/packManager.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
7 |
8 |
9 |
10 |
12 |
14 |
16 |
18 |
20 |
23 |
24 |
25 | <%- include('../sections/packManager/tabs.ejs'); %>
26 | <%- include('../sections/packManager/layoutTabs.ejs'); %>
27 |
--------------------------------------------------------------------------------
/web/js/FrogNews.js:
--------------------------------------------------------------------------------
1 | class FrogNews {
2 | // Получить новости
3 | static getNews = async () => {
4 | return await FrogRequests.get(NEWS_URL);
5 | }
6 |
7 | // Загрузить новости в UI
8 | static loadNewsToUI = async () => {
9 | $(".news .preloader").show();
10 | $(".news .news-list").hide();
11 | $(".news .news-list").html("");
12 |
13 | let [isSuccess, news] = await FrogNews.getNews();
14 | if(!isSuccess || !news){
15 | return false;
16 | }
17 | let placeholder = $(".news .placeholder")[0].outerHTML;
18 | placeholder = placeholder.replace(' placeholder"', "");
19 | // По placeholder`у добавляем новые элементы
20 | news.forEach((item) => {
21 | let preparedPlaceholder = placeholder.replaceAll("$1", item.title).replaceAll("$2", item.description).replaceAll("$3", item.date);
22 | if (typeof item.url !== "undefined") {
23 | preparedPlaceholder = preparedPlaceholder.replaceAll("$4", item.url).replace('class="link" style="display: none;"', 'class="link"');
24 | }
25 | let backgroundImageCSS = `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.82), rgb(0, 0, 0)), url("${item.picture}")`;
26 | $(".news .news-list").append(preparedPlaceholder);
27 | $(".news .news-list .news-item:last-child").css("background-image", backgroundImageCSS);
28 | })
29 | $(".news .preloader").hide();
30 | $(".news .news-list").show();
31 | animateCSSNode($(".news .news-list")[0], "fadeIn");
32 | return true;
33 | }
34 | }
--------------------------------------------------------------------------------
/web/css/spinner.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | border: 6px solid var(--theme-primaryBgLight);
3 | border-top: 6px solid var(--theme-primary);
4 | border-radius: 50%;
5 | width: 48px;
6 | height: 48px;
7 | animation: spin 1.3s cubic-bezier(0.5, 0, 0.2, 1) infinite;
8 | }
9 |
10 | /* Размеры спиннера */
11 | .spinner.small {
12 | width: 38px;
13 | height: 38px;
14 | border-width: 3px;
15 | }
16 |
17 | .spinner.large {
18 | width: 80px;
19 | height: 80px;
20 | border-width: 9px;
21 | }
22 |
23 | /* Виды спиннера */
24 | .spinner.transparent {
25 | border: 6px solid transparent;
26 | border-top: 6px solid var(--theme-primary);
27 | }
28 |
29 | .spinner.mode {
30 | border: 6px solid var(--bg-sub);
31 | border-top: 6px solid var(--text-sub);
32 | }
33 |
34 | @keyframes spin {
35 | 0% {
36 | transform: rotate(0deg);
37 | }
38 | 100% {
39 | transform: rotate(360deg);
40 | }
41 | }
42 |
43 | .line-spinner {
44 | display: inline-block;
45 | width: 100%;
46 | height: 16px;
47 | border-radius: 9999px;
48 |
49 | background: var(--theme-primaryBgLight);
50 |
51 | background-image: linear-gradient(90deg, var(--theme-primaryBgLight) 0%, var(--theme-primaryBgLighter) 50%, var(--theme-primaryBgLight) 100%) !important;
52 | filter: brightness(115%);
53 | background-position: 0 0;
54 | animation: gradAnim 3s linear infinite normal;
55 | }
56 |
57 | .line-spinner.custom-select {
58 | height: 100%;
59 | width: max-content;
60 | flex-grow: 1;
61 | pointer-events: none;
62 | }
63 |
64 | .line-spinner.small {
65 | height: 10px;
66 | }
--------------------------------------------------------------------------------
/verifyTranslations.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | let reference = JSON.parse(fs.readFileSync('./web/languages/ru.json').toString());
3 | let referenceKeys = Object.keys(flatten(reference));
4 |
5 | const languagesList = fs.readdirSync("./web/languages");
6 |
7 | languagesList.forEach((lang) => {
8 | if(lang !== "ru.json"){
9 | let langData = JSON.parse(fs.readFileSync(`./web/languages/${lang}`).toString());
10 | let flattenLang = Object.keys(flatten(langData));
11 | let difference = referenceKeys.filter(x => !flattenLang.includes(x));
12 | if(difference.length === 0){
13 | console.log(`Язык ${lang} не имеет проблем`);
14 | } else {
15 | console.log(`Язык ${lang} имеет ${difference.length} проблем:`);
16 | console.log(difference);
17 | }
18 | }
19 | })
20 |
21 | function traverseAndFlatten(currentNode, target, flattenedKey) {
22 | for (let key in currentNode) {
23 | if (currentNode.hasOwnProperty(key)) {
24 | let newKey;
25 | if (flattenedKey === undefined) {
26 | newKey = key;
27 | } else {
28 | newKey = flattenedKey + '.' + key;
29 | }
30 |
31 | let value = currentNode[key];
32 | if (typeof value === "object") {
33 | traverseAndFlatten(value, target, newKey);
34 | } else {
35 | target[newKey] = value;
36 | }
37 | }
38 | }
39 | }
40 |
41 | function flatten(obj) {
42 | let flattenedObject = {};
43 | traverseAndFlatten(obj, flattenedObject);
44 | return flattenedObject;
45 | }
--------------------------------------------------------------------------------
/web/views/versionsPosters.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | bolt
5 | {{commons.bigUpdates}}
6 |
7 |
8 |
9 |
10 |
12 | {{commons.version}} $1
13 | $2
14 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/web/css/tabs.css:
--------------------------------------------------------------------------------
1 | .tabs {
2 | display: flex;
3 | gap: 10px;
4 | align-items: center;
5 | overflow: visible;
6 | }
7 |
8 | .tabs.bg {
9 | background: var(--theme-primaryBgShadow);
10 | }
11 |
12 | .tabs.vertical {
13 | flex-direction: column;
14 | }
15 |
16 | .tabs.vertical .tab {
17 | width: 100%;
18 | justify-content: start;
19 | }
20 |
21 | .tabs .tab {
22 | background: var(--theme-primaryBgLight);
23 | width: max-content;
24 | height: 40px;
25 | border-radius: 8px;
26 |
27 | display: flex;
28 | align-items: center;
29 | justify-content: center;
30 | gap: 8px;
31 |
32 | padding: 24px 16px;
33 | font-size: 14pt;
34 | }
35 |
36 | .tabs .tab .icon {
37 | display: flex;
38 | align-items: center;
39 | justify-content: center;
40 |
41 | width: 24px;
42 | height: 24px;
43 | background: var(--theme-primaryBgLighter);
44 | border-radius: 8px;
45 | }
46 |
47 | .tabs .tab.active .icon {
48 | background: var(--theme-primaryLighter);
49 | }
50 |
51 | .tabs .tab .icon * {
52 | font-size: 16px;
53 | }
54 |
55 | .tabs .tab:not(.active):hover {
56 | cursor: pointer;
57 | filter: brightness(105%);
58 | }
59 |
60 | html.light .tabs .tab:not(.active):hover {
61 | filter: brightness(95%);
62 | }
63 |
64 | .tabs .tab * {
65 | color: var(--text-color);
66 | }
67 |
68 | .tabs .tab:not(.active):hover {
69 | filter: brightness(135%);
70 | }
71 |
72 | .tabs .tab.active {
73 | background: var(--theme-primary);
74 | border: none;
75 | box-shadow: var(--theme-primaryBgLight) 0 2px 16px;
76 | }
77 |
78 | .tabs .tab.active * {
79 | color: var(--theme-text);
80 | }
--------------------------------------------------------------------------------
/web/js/FrogUpdater.js:
--------------------------------------------------------------------------------
1 | class FrogUpdater {
2 | // Забиндить эвенты обновлений
3 | static bindUpdate = () => {
4 | ipcRenderer.once("update-available", () => {
5 | FrogUpdater.onUpdateAvailable();
6 | });
7 | ipcRenderer.once("update-downloaded", () => {
8 | FrogUpdater.onUpdateDownloaded();
9 | });
10 | }
11 |
12 | // При нахождении обновления - показать уведомление
13 | static onUpdateAvailable = () => {
14 | let $notifyElem = $("#updateNotify");
15 | let $notifyElemBtn = $notifyElem.find("button:not(.transparent)");
16 | let $notifyElemText = $notifyElem.find(".description");
17 | FrogCollector.writeLog(`Updater: New version found`);
18 | $notifyElem.show();
19 | $notifyElem.addClass("animate__animated animate__fadeIn");
20 | $notifyElemBtn.hide();
21 | $notifyElemText.text();
22 | setTimeout(() => {
23 | $notifyElem.removeClass("animate__animated animate__fadeIn");
24 | }, 1000);
25 | }
26 |
27 | // После скачивания обновления - изменить уведомление
28 | static onUpdateDownloaded = () => {
29 | let $notifyElem = $("#updateNotify");
30 | let $notifyElemBtn = $notifyElem.find("button:not(.transparent)");
31 | let $notifyElemText = $notifyElem.find(".description");
32 | FrogCollector.writeLog(`Updater: New version downloaded, ready to restart`);
33 | $notifyElemBtn.show();
34 | $notifyElemText.text(MESSAGES.updater.ready);
35 | }
36 |
37 | // Установить обновление
38 | static installUpdate = () => {
39 | ipcRenderer.send("install-update");
40 | }
41 | }
--------------------------------------------------------------------------------
/web/views/flymenu.css:
--------------------------------------------------------------------------------
1 | /* Flyout menu */
2 | .flymenu {
3 | width: 78px !important;
4 | height: 100% !important;
5 |
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | flex-direction: column;
10 |
11 | gap: 16px;
12 | }
13 |
14 | .flymenu .menu-wrapped {
15 | display: flex;
16 | flex-direction: column;
17 | align-items: center;
18 | justify-content: center;
19 | overflow: visible;
20 | gap: 12px;
21 | flex-grow: 1;
22 | }
23 |
24 | .flymenu .item {
25 | width: 56px;
26 | height: 56px;
27 | border-radius: 16px;
28 |
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | flex-direction: column;
33 |
34 | background: var(--theme-primaryBgLight);
35 | color: var(--theme-text);
36 | }
37 |
38 | .flymenu .item:not(.active):hover {
39 | cursor: pointer;
40 | filter: brightness(112%);
41 | box-shadow: var(--theme-primaryBgLight) 0 0 24px;
42 | }
43 |
44 | html.light .flymenu .item:not(.active):hover {
45 | filter: brightness(90%);
46 | }
47 |
48 | .flymenu .item.active {
49 | background: var(--theme-primary);
50 | box-shadow: var(--theme-primaryDarker) 0 0 48px;
51 | }
52 |
53 | .flymenu .item.active,
54 | .flymenu .item.active * {
55 | color: var(--theme-text);
56 | font-size: 24px;
57 | }
58 |
59 | .flymenu-tooltip {
60 | z-index: 9999;
61 | position: absolute;
62 | left: 0;
63 | right: 0;
64 | bottom: 0;
65 | top: 0;
66 | border-radius: 16px;
67 |
68 | width: max-content;
69 | height: max-content;
70 | transition: top 0s, left 0s !important;
71 | pointer-events: none !important;
72 | }
--------------------------------------------------------------------------------
/web/sections/settings/game.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | work_update
4 |
5 |
{{settings.game.autoInstallDeps}}
6 |
10 |
11 |
12 |
13 |
14 | terminal
15 |
16 |
{{settings.game.consoleOnStart}}
17 |
21 |
22 |
23 |
24 |
25 | bug_report
26 |
27 |
{{settings.game.consoleOnCrash}}
28 |
32 |
33 |
34 |
35 |
36 | hide
37 |
38 |
{{settings.game.hideOnStart}}
39 |
43 |
--------------------------------------------------------------------------------
/web/views/flyout.css:
--------------------------------------------------------------------------------
1 | .flyout, .flymenu {
2 | background: var(--theme-primaryBg);
3 | width: 80%;
4 | height: 78px;
5 | padding: 10px;
6 | border-radius: 21px;
7 |
8 | display: flex;
9 | align-items: center;
10 | justify-content: center;
11 | gap: 10px;
12 |
13 | box-shadow: var(--theme-primaryBgShadow) 0 2px 24px;
14 | }
15 |
16 | .flyout > div, button {
17 | height: 56px;
18 | border-radius: 11px !important;
19 | }
20 |
21 | .flyout #playButton {
22 | width: 15vw;
23 | min-width: 160px;
24 | }
25 |
26 | .flyout #accountSelect {
27 | width: 20vw;
28 | }
29 |
30 | .flyout .flyout-mode {
31 | animation-duration: .24s !important;
32 | }
33 |
34 | .flyout #stopGameButton {
35 | background: var(--error);
36 | }
37 |
38 | .flyout #stopGameButton,
39 | .flyout #stopGameButton * {
40 | color: white !important;
41 | }
42 |
43 | .flyout #stopGameButton:hover {
44 | filter: brightness(87%);
45 | }
46 |
47 | /* Hide divs */
48 | .flyout > div.hidden {
49 | display: none;
50 | }
51 |
52 | .flyout .progress-pill,
53 | .flyout .spinner-wrapper,
54 | .flyout #versionSelect {
55 | flex-grow: 1;
56 | }
57 |
58 | /* Spinner wrapper */
59 | .flyout .spinner-wrapper {
60 | display: flex;
61 | align-items: center;
62 | justify-content: start;
63 | gap: 12px;
64 | flex-direction: row;
65 | }
66 |
67 | .flyout .spinner-wrapper .title {
68 | font-weight: 400;
69 | font-size: 14pt;
70 | }
71 |
72 | /* Flyout start mode */
73 | .flyout.start-mode #accountSelect,
74 | .flyout.start-mode #versionSelect {
75 | pointer-events: none !important;
76 | cursor: not-allowed;
77 | }
78 |
79 | .flyout.start-mode #playButton {
80 | display: none;
81 | }
--------------------------------------------------------------------------------
/web/css/toast.css:
--------------------------------------------------------------------------------
1 | .toast {
2 | min-width: 18vw;
3 | max-height: 40vh;
4 | max-width: 40vw;
5 | width: 100%;
6 | height: max-content;
7 | background: var(--theme-primaryBg);
8 | border-radius: 8px;
9 | padding: 10px 16px;
10 | display: flex;
11 | align-items: center;
12 | justify-content: start;
13 | flex-direction: row;
14 | cursor: pointer;
15 | }
16 |
17 | .toast .icon-bg {
18 | width: 38px;
19 | height: 38px;
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | background: var(--theme-primaryBgLight);
24 | border-radius: 8px;
25 | margin-right: 16px;
26 | }
27 |
28 | .toast .content {
29 | display: flex;
30 | flex-direction: column;
31 | }
32 |
33 | .toast .content .description {
34 | margin-top: 2px;
35 | }
36 |
37 | .toast .icon-bg * {
38 | color: var(--text-color);
39 | font-size: 21pt;
40 | }
41 |
42 | .toast .icon-bg.circle {
43 | border-radius: 50%;
44 | }
45 |
46 | .toast .icon-bg {
47 | background: var(--theme-primaryBgLighter);
48 | }
49 |
50 | .toast .icon-bg.colored * {
51 | color: var(--theme-primary);
52 | }
53 |
54 | .toast .caption {
55 | font-size: 16pt;
56 | font-weight: 500;
57 | color: var(--text-color);
58 | }
59 |
60 | .toast .description {
61 | font-size: 13pt;
62 | color: #9d9d9d;
63 | }
64 |
65 | #toasts-pool {
66 | display: flex;
67 | flex-direction: column;
68 | justify-content: center;
69 |
70 | position: absolute;
71 | z-index: 3000000;
72 |
73 | max-height: 100vh;
74 | max-width: 45vw;
75 | height: max-content;
76 | width: max-content;
77 |
78 | overflow: hidden;
79 | gap: 12px;
80 |
81 | top: 24px;
82 | left: 24px;
83 | }
--------------------------------------------------------------------------------
/web/views/news.css:
--------------------------------------------------------------------------------
1 | .news {
2 | display: flex;
3 | align-items: center;
4 | overflow-x: auto;
5 | width: 100%;
6 | height: 28vh;
7 | }
8 |
9 | .news .preloader {
10 | width: 100%;
11 | height: 100%;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | flex-direction: column;
16 | gap: 12px;
17 | }
18 |
19 | .news-list {
20 | display: flex;
21 | width: 82vw;
22 | height: 100%;
23 | gap: 12px;
24 | scroll-snap-align: center;
25 | scroll-snap-type: x mandatory;
26 |
27 | overflow: auto;
28 | }
29 |
30 | .news-list .news-item {
31 | flex: 0 0 auto;
32 | scroll-snap-align: start;
33 | }
34 |
35 | .news-list::-webkit-scrollbar-thumb,
36 | .news-list::-webkit-scrollbar,
37 | .news-list::-webkit-scrollbar-track {
38 | height: 8px !important;
39 | }
40 |
41 | .news-list::-webkit-scrollbar-thumb {
42 | background: var(--theme-primaryBgLighter);
43 | }
44 |
45 | .news-list::-webkit-scrollbar-track {
46 | background: transparent;
47 | }
48 |
49 | /* News item */
50 | .news .news-item {
51 | padding: 18px;
52 | border-radius: 12px;
53 |
54 | background-size: cover;
55 | background-repeat: no-repeat;
56 | background-position: center;
57 |
58 | display: flex;
59 | flex-direction: column;
60 | justify-content: end;
61 |
62 | width: 32vw;
63 | height: 27vh;
64 | }
65 |
66 | .news .news-item .title {
67 | font-size: 22pt;
68 | font-weight: 700;
69 | color: white;
70 | }
71 |
72 | .news .news-item .description {
73 | font-size: 13pt;
74 | color: rgba(235, 235, 235);
75 | }
76 |
77 | .news .news-item .date {
78 | font-size: 11pt;
79 | font-weight: 400;
80 | color: rgba(180, 180, 180);
81 | text-align: end;
82 | }
--------------------------------------------------------------------------------
/web/css/alert.css:
--------------------------------------------------------------------------------
1 | .alert-modal {
2 | width: 100%;
3 | height: 100%;
4 | z-index: 40;
5 | display: block;
6 |
7 | position: absolute;
8 | left: 0;
9 | right: 0;
10 | top: 0;
11 | bottom: 0;
12 | backdrop-filter: blur(8px);
13 | border-radius: 12px;
14 | }
15 |
16 | .alert-modal .alert-window {
17 | position: absolute;
18 | left: 0;
19 | right: 0;
20 | top: 0;
21 | bottom: 0;
22 | width: max-content;
23 | max-width: 48vw;
24 | min-width: 23vw;
25 | height: max-content;
26 | padding: 32px;
27 | border-radius: 16px;
28 | z-index: 41;
29 | background: var(--theme-primaryBg);
30 | box-shadow: rgba(0, 0, 0, 0.25) 0 54px 55px,
31 | rgba(0, 0, 0, 0.12) 0 -12px 30px, rgba(0, 0, 0, 0.12) 0 4px 6px,
32 | rgba(0, 0, 0, 0.17) 0 12px 13px, rgba(0, 0, 0, 0.09) 0 -3px 5px;
33 |
34 | display: flex;
35 | flex-direction: column;
36 | align-items: center;
37 | justify-content: center;
38 | gap: 16px;
39 | margin: auto;
40 | }
41 |
42 | .alert-modal .alert-window .alert-icon {
43 | display: flex;
44 | align-items: center;
45 | justify-content: center;
46 |
47 | border-radius: 8px;
48 | padding: 8px;
49 | background: var(--theme-primaryBgLight);
50 | height: 64px;
51 | width: 64px;
52 | }
53 |
54 | .alert-modal .alert-window .alert-icon .material-symbols-outlined {
55 | font-size: 26pt;
56 | color: var(--text-color);
57 | }
58 |
59 | .alert-modal .alert-window .alert-caption {
60 | font-weight: 700;
61 | font-size: 15pt;
62 | color: var(--text-color);
63 | text-align: center;
64 | }
65 |
66 | .alert-modal .alert-window .alert-description {
67 | font-weight: 500;
68 | font-size: 14pt;
69 | color: var(--text-sub);
70 | text-align: center;
71 | }
72 |
--------------------------------------------------------------------------------
/web/modals/console.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | terminal
6 |
{{console.title}}
7 |
8 |
11 |
12 |
13 |
17 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/web/modals/settings.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
settings
6 |
{{settings.title}}
7 |
10 |
11 | format_paint
12 | {{settings.sections.personalization}}
13 |
14 |
15 |
16 |
17 | <%- include('../sections/settings/tabs'); %>
18 |
19 |
20 |
21 |
22 | <%- include('../sections/settings/personalization'); %>
23 |
24 |
25 |
26 |
27 | <%- include('../sections/settings/game'); %>
28 |
29 |
30 |
31 |
32 | <%- include('../sections/settings/startup'); %>
33 |
34 |
35 |
36 |
37 | <%- include('../sections/settings/java'); %>
38 |
39 |
40 |
41 |
42 | <%- include('../sections/settings/about'); %>
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/web/js/FrogToasts.js:
--------------------------------------------------------------------------------
1 | class FrogToasts {
2 | // Функция отправки нового уведомления
3 | static create(text, icon = "info", description = "", duration = 5000, iconClasses = "", callback = () => {
4 | }) {
5 | let toastsPoolElement = $("#toasts-pool");
6 | let newID = this.generateID();
7 | let toastCode = "";
8 | if (iconClasses !== "") {
9 | toastCode = toastCode + "
" + icon + "
";
10 | } else {
11 | toastCode = toastCode + "
" + icon + "
";
12 | }
13 | if (description !== "") {
14 | toastCode = toastCode + "
" + text + "
" + description + "
";
15 | } else {
16 | toastCode = toastCode + "
" + text + "
";
17 | }
18 | toastCode = toastCode + "
";
19 | toastsPoolElement.append(toastCode);
20 | $("#toast-" + newID).on("click", function () {
21 | $(this).remove();
22 | callback();
23 | });
24 | if (duration > 0) {
25 | $("#toast-" + newID)
26 | .delay(duration)
27 | .queue(function () {
28 | animateCSSNode($(this)[0], "fadeOut", false).then(() => {
29 | $(this).remove();
30 | });
31 | });
32 | }
33 | }
34 |
35 | // Получить ID для нового уведомления
36 | static generateID() {
37 | return $("#toasts-pool .toast").length;
38 | }
39 |
40 | // Удалить все уведомления
41 | static removeAll() {
42 | $("#toasts-pool").html("");
43 | }
44 | }
--------------------------------------------------------------------------------
/web/css/custom-select.css:
--------------------------------------------------------------------------------
1 | .custom-select {
2 | background: var(--theme-primaryBgLight);
3 | cursor: pointer;
4 |
5 | border-radius: 6px;
6 | padding: 0 6px;
7 | height: 56px;
8 | }
9 |
10 | .custom-select.lighter {
11 | background: var(--theme-primaryBgLighter);
12 | }
13 |
14 | select.custom-select {
15 | color: var(--text-color);
16 | padding: 6px 12px;
17 | font-size: 14pt;
18 | font-weight: 600;
19 | }
20 |
21 | .custom-select:hover {
22 | filter: brightness(110%);
23 | background: var(--theme-primaryBgLight) !important;
24 | }
25 |
26 | /* Icon and description mode */
27 | .custom-select.icon-and-description {
28 | display: grid;
29 | grid-template-columns: 40px 1fr max-content;
30 | grid-template-rows: max-content max-content;
31 | grid-auto-columns: 1fr;
32 | gap: 0 12px;
33 | grid-auto-flow: row;
34 | align-content: center;
35 | grid-template-areas:
36 | "icon title button"
37 | "icon description button";
38 | }
39 |
40 | .custom-select.icon-and-description .icon {
41 | justify-self: center;
42 | align-self: center;
43 | grid-area: icon;
44 |
45 | width: 40px;
46 | height: 40px;
47 | border-radius: 8px;
48 | }
49 |
50 | .custom-select.icon-and-description .title {
51 | grid-area: title;
52 | font-size: 14pt;
53 | font-weight: 500;
54 | }
55 |
56 | .custom-select.icon-and-description .button {
57 | grid-area: button;
58 | justify-self: center;
59 | align-self: center;
60 | padding: 2px;
61 | border-radius: 9999px;
62 | background: var(--theme-primaryBgLighter);
63 |
64 | display: flex;
65 | align-items: center;
66 | justify-content: center;
67 | }
68 |
69 | .custom-select.icon-and-description .description {
70 | align-self: start;
71 | grid-area: description;
72 |
73 | color: rgb(150, 150, 150);
74 | text-transform: uppercase;
75 | font-size: 9pt;
76 | font-weight: 700;
77 | }
--------------------------------------------------------------------------------
/web/sections/packManager/tabs.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | deployed_code_update
5 |
6 |
{{packs.filters.mods}}
7 |
8 |
9 |
10 | globe_asia
11 |
12 |
{{packManager.tabs.worlds}}
13 |
14 |
15 |
16 | landscape
17 |
18 |
{{packs.filters.rp}}
19 |
20 |
21 |
22 | light_mode
23 |
24 |
{{packs.filters.shaders}}
25 |
26 |
32 |
33 |
34 | update
35 |
36 |
{{packManager.tabs.updates}}
37 |
38 |
39 |
43 |
44 |
48 |
--------------------------------------------------------------------------------
/web/sections/packsAndMods/wrappedModsList.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |

7 |
$2
8 |
$3
9 |
10 | download
11 | $4
12 | favorite
13 | $5
14 |
15 |
24 |
25 |
32 |
33 |
34 |
35 |
36 |
{{loading.progress}}
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/web/modals/installMods.js:
--------------------------------------------------------------------------------
1 | let packs__firstAppear = true;
2 |
3 | $(function () {
4 | $(document).on("showModalEvent", (e) => {
5 | if (e.originalEvent.detail.modal === "installMods") {
6 | if (packs__firstAppear === true) {
7 | packs__firstAppear = false;
8 | FrogPacksUI.reloadAll(true);
9 | } else {
10 | FrogPacksUI.refreshDirectorySelect();
11 | }
12 | }
13 | })
14 |
15 | $("#modal-installMods input.search").on("change", function () {
16 | FrogPacksUI.reloadAll(true);
17 | })
18 |
19 | $("#modal-installMods #packs_dirList").on("change", function () {
20 | FrogPacksUI.reloadAll(true);
21 | })
22 |
23 | // Прогрузка при скролле
24 | $("#modal-installMods .packs-wrapper").scroll(function () {
25 | let wrapper = $(this)[0];
26 | if (wrapper.offsetHeight === wrapper.scrollHeight || packs_scrollIsLoading) {
27 | return false;
28 | }
29 | if (wrapper.offsetHeight + wrapper.scrollTop >= wrapper.scrollHeight) {
30 | packs_scrollIsLoading = true;
31 | FrogPacksUI.loadMore();
32 | return true;
33 | }
34 | });
35 | })
36 |
37 | // Переключение табов
38 | $("#modal-installMods .tabs.sub .tab").click(function () {
39 | if ($(this).hasClass("active")) {
40 | return;
41 | }
42 |
43 | $("#modal-installMods .tabs.sub .tab.active").removeClass("active");
44 | $(this).addClass("active");
45 | let tabName = $(this).data("tab");
46 |
47 | let tabIcon = $(this).find(".material-symbols-outlined").html();
48 | let tabText = $(this).find("span:not(.material-symbols-outlined)").text();
49 |
50 | $("#packs_currentSection span:not(.material-symbols-outlined)").text(tabText);
51 | $("#packs_currentSection .material-symbols-outlined").html(tabIcon);
52 |
53 | FrogPacksUI.setCurrentMode(tabName);
54 | FrogPacksUI.reloadAll(true);
55 | })
--------------------------------------------------------------------------------
/web/views/flymenu.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
25 |
26 |
--------------------------------------------------------------------------------
/web/css/modal.css:
--------------------------------------------------------------------------------
1 | .modal {
2 | position: relative;
3 | width: 100%;
4 | height: 100%;
5 | background: var(--theme-primaryBg);
6 | z-index: 3;
7 |
8 | border-radius: 16px;
9 | box-shadow: rgba(0, 0, 0, 0.16) 0 10px 36px 0, rgba(0, 0, 0, 0.06) 0 0 0 1px;
10 | padding: 3%;
11 |
12 | max-height: 73vh;
13 | }
14 |
15 | .modal-overlay {
16 | position: absolute;
17 | left: 0;
18 | right: 0;
19 | bottom: 0;
20 | top: 0;
21 | z-index: 4;
22 | width: 100vw;
23 | height: 100vh;
24 | backdrop-filter: blur(2px) brightness(85%);
25 | display: none;
26 | }
27 |
28 | body:has(.modal.overlay:not([style*="display: none;"])) > .modal-overlay {
29 | display: block;
30 | }
31 |
32 | .modal.centered {
33 | position: absolute;
34 | left: 0;
35 | right: 0;
36 | bottom: 0;
37 | top: 0;
38 | margin: auto;
39 | z-index: 10;
40 | }
41 |
42 | .modal.overlay:not(.centered) {
43 | z-index: 11 !important;
44 | }
45 |
46 | .modal .content {
47 | padding: 1.5% 0 0 0;
48 | }
49 |
50 | .modal h1, .modal h2, .modal h3, .modal h4, .modal h5, .modal h6 {
51 | padding: 0;
52 | margin: 0;
53 | }
54 |
55 | /* Title chip */
56 | .modal .title-chip {
57 | width: max-content;
58 | height: max-content;
59 | display: flex;
60 | align-items: center;
61 | justify-content: center;
62 |
63 | gap: 6px;
64 | background: var(--theme-primaryBgLight);
65 | padding: 8px;
66 | border-radius: 24px;
67 |
68 | font-size: 12pt;
69 | }
70 |
71 | .modal .title-chip .icon {
72 | background: var(--theme-primaryBgLighter);
73 | border-radius: 9999px;
74 |
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | width: 24px;
79 | height: 24px;
80 | font-size: 18px;
81 | }
82 |
83 | /* Icon */
84 | .modal > div:nth-child(1) > .material-symbols-outlined {
85 | background: var(--theme-primaryBgLight);
86 | padding: 10px;
87 | border-radius: 8px;
88 | }
89 |
90 | .modal > div:nth-child(1) {
91 | margin-bottom: 6px;
92 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FrogLauncher
2 |
3 | FrogLauncher - это Minecraft-лаунчер, который позволяет запускать любые версии игры, устанавливать моды и создавать модпаки. Поддерживает как пиратские, так и официальные аккаунты, а также имеет различные настройки оформления.
4 |
5 | ## Важные ссылки
6 | - [Сайт лаунчера](https://froglauncher.ru)
7 | - [Последний релиз лаунчера](https://github.com/Seeroy/FrogLauncher2/releases/latest)
8 | - [Плагин на сервер для поддержки системы скинов лаунчера](https://github.com/Seeroy/FrogLauncher-Skins-Plugin)
9 |
10 | ## Особенности
11 |
12 | - **Запуск любых (почти :D) версий Minecraft**
13 | - **Установка модов**: Простая установка модов с сервиса Modrinth
14 | - **Создание модпаков**: Доступно создание своих модпаков, а так же установка их с Modrinth
15 | - **Поддержка аккаунтов**: Используйте как пиратские, так и официальные аккаунты Minecraft
16 | - **Широкие возможности кастомизации**: Возможно изменение главного цвета, изображения фона лаунчера, тёмный и светлый режим
17 |
18 | ## Технологии
19 |
20 | FrogLauncher разработан с использованием следующих технологий:
21 |
22 | - **Electron**
23 | - **EJS**
24 | - **CSS**
25 | - **jQuery**
26 | - **[minecraft-launcher-core](https://github.com/Pierce01/MinecraftLauncher-core)**
27 | - **[tomate-loaders](https://github.com/doublekekse/tomate-loaders)**
28 | - **[MSMC](https://github.com/Hanro50/MSMC)**
29 |
30 | ## Установка
31 |
32 | 1. Клонируйте репозиторий:
33 | ```bash
34 | git clone https://github.com/Seeroy/FrogLauncher2.git
35 | cd FrogLauncher2
36 | ```
37 |
38 | 2. Установите зависимости:
39 | ```bash
40 | npm install
41 | ```
42 |
43 | 3. Запустите лаунчер:
44 | ```bash
45 | npm start
46 | ```
47 |
48 | 4. Так же вы можете использовать режим разработчика:
49 | ```bash
50 | npm run dev
51 | ```
52 |
53 | ## Лицензия
54 |
55 | Этот проект лицензируется под лицензией GPL-3.0. Подробности можно найти в файле [LICENSE](LICENSE).
56 |
57 | ---
58 |
59 | **FrogLauncher** - отличный выбор для тех, кто хочет разнообразить и улучшить свой Minecraft-опыт!
60 |
--------------------------------------------------------------------------------
/web/sections/settings/java.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | motion_photos_auto
4 |
5 |
6 |
{{settings.java.auto.title}}
7 |
{{settings.java.auto.description}}
8 |
9 |
13 |
14 |
15 |
16 |
17 | folder
18 |
19 |
20 |
{{settings.java.user}}
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
--------------------------------------------------------------------------------
/web/css/button.css:
--------------------------------------------------------------------------------
1 | /* Обычная кнопка */
2 | button {
3 | padding: 8px 18px;
4 | border-radius: 6px;
5 |
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | gap: 8px;
10 | flex-direction: row;
11 | font-size: 18pt;
12 | cursor: pointer;
13 |
14 | height: max-content;
15 | width: max-content;
16 |
17 | background: var(--theme-primary);
18 | }
19 |
20 | button, button * {
21 | color: var(--theme-text);
22 | }
23 |
24 | button:hover {
25 | background: var(--theme-primaryDarker);
26 | }
27 |
28 | .button.pill {
29 | border-radius: 24px !important;
30 | padding: 6px 18px;
31 | }
32 |
33 | .button.pill.small {
34 | padding: 4px 26px;
35 | font-size: 15pt;
36 | }
37 |
38 | /* Квадратная кнопка */
39 | button.square {
40 | width: 64px;
41 | height: 64px;
42 |
43 | padding: 0 !important;
44 | margin: 0 !important;
45 | display: flex;
46 | align-items: center;
47 | justify-content: center;
48 | }
49 |
50 | button.square:not(.small) .material-symbols-outlined {
51 | font-size: 24pt;
52 | }
53 |
54 | button.square.small {
55 | width: 48px;
56 | height: 48px;
57 | }
58 |
59 | /* Прозрачная кнопка */
60 | button.transparent {
61 | background: transparent;
62 | color: var(--text-color);
63 | }
64 |
65 | button.transparent:hover {
66 | background: var(--theme-primaryBgLight);
67 | }
68 |
69 | /* Кнопка sub-цвета */
70 | button.sub {
71 | background: var(--bg-sub);
72 | color: var(--text-color);
73 | }
74 |
75 | button.sub:hover {
76 | filter: brightness(90%);
77 | }
78 |
79 | /* Outline-кнопка */
80 | button.outline {
81 | color: var(--text-sub);
82 | background: transparent;
83 | border: 2px solid var(--text-sub) !important;
84 | opacity: 0.77;
85 | }
86 |
87 | button.outline,
88 | button.outline * {
89 | color: var(--text-sub);
90 | }
91 |
92 | button.outline:hover {
93 | border-color: var(--text-color);
94 | opacity: 1;
95 | }
96 |
97 | button.outline:hover,
98 | button.outline:hover * {
99 | color: var(--text-color);
100 | }
--------------------------------------------------------------------------------
/web/views/versionsPosters.css:
--------------------------------------------------------------------------------
1 | .versionsPostersWrapper {
2 | display: flex;
3 | align-items: center;
4 | overflow-y: auto;
5 | width: 100%;
6 | height: max-content;
7 | }
8 |
9 | .versionsPosters {
10 | display: grid;
11 | grid-auto-columns: 1fr;
12 | grid-template-columns: 1fr 1fr;
13 | grid-auto-rows: max-content;
14 | gap: 12px;
15 | grid-template-areas:
16 | ". .";
17 | justify-content: center;
18 | align-content: center;
19 |
20 | grid-auto-flow: dense;
21 |
22 | width: 82vw;
23 | height: max-content;
24 | scroll-snap-align: center;
25 | scroll-snap-type: x mandatory;
26 |
27 | overflow: auto;
28 | }
29 |
30 | .versionsPosters.start-mode button {
31 | display: none !important;
32 | }
33 |
34 | .versionsPostersWrapper *::-webkit-scrollbar-thumb,
35 | .versionsPostersWrapper *::-webkit-scrollbar,
36 | .versionsPostersWrapper *::-webkit-scrollbar-track {
37 | height: 8px !important;
38 | }
39 |
40 | .versionsPostersWrapper *::-webkit-scrollbar-thumb {
41 | background: var(--theme-primaryBgLighter);
42 | }
43 |
44 | .versionsPostersWrapper *::-webkit-scrollbar-track {
45 | background: transparent;
46 | }
47 |
48 | /* Version item */
49 | .versionsPosters .item {
50 | display: grid;
51 | grid-auto-columns: 1fr;
52 | grid-template-columns: max-content 1fr max-content;
53 | grid-template-rows: 1fr max-content max-content;
54 | gap: 2px;
55 | grid-template-areas:
56 | ". . ."
57 | "title title button"
58 | "description description button";
59 | justify-content: center;
60 | align-content: center;
61 |
62 | padding: 18px;
63 | border-radius: 12px;
64 |
65 | background-size: cover;
66 | background-repeat: no-repeat;
67 | background-position: center;
68 |
69 | width: 100%;
70 | height: 24vh;
71 |
72 | flex: 0 0 auto;
73 | scroll-snap-align: start;
74 | }
75 |
76 | .versionsPosters .item .title {
77 | grid-area: title;
78 | font-size: 22pt;
79 | font-weight: 700;
80 | color: white;
81 | }
82 |
83 | .versionsPosters .item .description {
84 | grid-area: description;
85 | font-size: 13pt;
86 | color: rgba(235, 235, 235);
87 | }
88 |
89 | .versionsPosters .item button {
90 | grid-area: button;
91 | width: 48px;
92 | height: 48px;
93 |
94 | align-self: end;
95 | }
--------------------------------------------------------------------------------
/web/modals/changelog.js:
--------------------------------------------------------------------------------
1 | const changelog_pageLimit = 25;
2 | let changelog_currentPage = 1;
3 | let changelog_endOfPage = false;
4 |
5 | const CHANGELOG_PRELOADER_PLACEHOLDER = `${MESSAGES.loading.progress}
`;
6 | const CHANGELOG_FAILED_PLACEHOLDER = `${MESSAGES.loading.failed}
`;
7 | const CHANGELOG_ITEM_PLACEHOLDER = `$1
$2
$3
`;
8 |
9 | $(function () {
10 | $(document).on("showModalEvent", (e) => {
11 | if (e.originalEvent.detail.modal === "changelog" && $(".list .item").length === 0) {
12 | FrogChangelogUI.loadReleases();
13 | }
14 | })
15 | })
16 |
17 | $("#modal-changelog .wrapper").scroll(function () {
18 | let wrapper = $("#modal-changelog .wrapper")[0];
19 | if (wrapper.offsetHeight + wrapper.scrollTop >= wrapper.scrollHeight) {
20 | changelog_currentPage++;
21 | FrogChangelogUI.loadReleases();
22 | }
23 | });
24 |
25 | class FrogChangelogUI {
26 | static getReleases = (cb) => {
27 | $.get(`https://api.github.com/repos/${REPO_NAME}/releases?per_page=${changelog_pageLimit}&page=${changelog_currentPage}`, cb).fail(() => {
28 | $(".list").html(CHANGELOG_FAILED_PLACEHOLDER);
29 | });
30 | }
31 |
32 | static loadReleases = () => {
33 | if (changelog_endOfPage === true) {
34 | return;
35 | }
36 |
37 | FrogChangelogUI.addPlaceholder();
38 | FrogChangelogUI.getReleases((releases) => {
39 | if (releases !== false && Array.isArray(releases)) {
40 | releases.forEach((release) => {
41 | $("#modal-changelog .list").append(CHANGELOG_ITEM_PLACEHOLDER.replaceAll("$1", release.name).replaceAll("$2", release.body.replaceAll("\n", "
")).replaceAll("$3", release.published_at));
42 | if (release.tag_name === "v2.0.0") {
43 | changelog_endOfPage = true;
44 | }
45 | });
46 | }
47 | FrogChangelogUI.removePlaceholder();
48 | })
49 | }
50 |
51 | static addPlaceholder = () => {
52 | return $("#modal-changelog .list").append(CHANGELOG_PRELOADER_PLACEHOLDER);
53 | }
54 |
55 | static removePlaceholder = () => {
56 | return $("#modal-changelog .list .placeholder").remove();
57 | }
58 | }
--------------------------------------------------------------------------------
/web/js/FrogPlayStats.js:
--------------------------------------------------------------------------------
1 | let gameStatsData;
2 | let currentGameCounter;
3 |
4 | class FrogPlayStats {
5 | // Загрузить всю сохранённую статистику из конфига
6 | static getAllStats = () => {
7 | gameStatsData = FrogConfig.read("gameStatistics", {});
8 | return gameStatsData;
9 | }
10 |
11 | // Получить статистику по версии
12 | static getByID = (versionId) => {
13 | if (typeof gameStatsData[versionId] === "undefined") {
14 | return false;
15 | }
16 |
17 | return gameStatsData[versionId];
18 | }
19 |
20 | // Получить список версий отсортированный по последнему времени запуска
21 | static getByLastStarted = () => {
22 | if (Object.values(gameStatsData).length === 0) return [];
23 |
24 | return Object.values(Object.entries(gameStatsData)
25 | .sort(([, a], [, b]) => b.lastStart - a.lastStart)
26 | .reduce((r, [k, v]) => ({...r, [k]: v}), {}));
27 | }
28 |
29 | // Получить список версий отсортированный по времени в игре
30 | static getByIngameTime = () => {
31 | if (Object.values(gameStatsData).length === 0) return [];
32 |
33 | return Object.values(Object.entries(gameStatsData)
34 | .sort(([, a], [, b]) => b.inGame - a.inGame)
35 | .reduce((r, [k, v]) => ({...r, [k]: v}), {}));
36 | }
37 |
38 | // Сохранить статистику
39 | static saveStats = () => {
40 | return FrogConfig.write("gameStatistics", gameStatsData);
41 | }
42 |
43 | // Вызывается при запуске игры
44 | static onGameLaunch = (versionId) => {
45 | if (typeof gameStatsData[versionId] === "undefined") {
46 | gameStatsData[versionId] = {
47 | id: versionId,
48 | parsed: FrogVersionsManager.parseVersionID(versionId),
49 | starts: 0,
50 | inGame: 0,
51 | lastStart: 0
52 | }
53 | }
54 | gameStatsData[versionId].starts++;
55 | gameStatsData[versionId].lastStart = Date.now();
56 | currentGameCounter = setInterval(() => {
57 | gameStatsData[versionId].inGame++;
58 | }, 1000);
59 | FrogPlayStats.saveStats();
60 | return true;
61 | }
62 |
63 | // Вызывается при завершении игры
64 | static onGameClose = () => {
65 | clearInterval(currentGameCounter);
66 | FrogPlayStats.saveStats();
67 | return true;
68 | }
69 | }
--------------------------------------------------------------------------------
/web/css/globals.css:
--------------------------------------------------------------------------------
1 | /* Элементы */
2 | @import "./resets.css";
3 | @import "./layout.css";
4 | @import "./progress.css";
5 | @import "./slider.css";
6 | @import "./misc.css";
7 | @import "./button.css";
8 | @import "./tabs.css";
9 | @import "./custom-select.css";
10 | @import "./checkbox.css";
11 | @import "./checksList.css";
12 | @import "./spinner.css";
13 | @import "./modal.css";
14 | @import "./colorPicker.css";
15 | @import "./switch.css";
16 | @import "./scrollbar.css";
17 | @import "./chips.css";
18 | @import "./input.css";
19 | @import "./toast.css";
20 | @import "./alert.css";
21 | @import "./link.css";
22 | @import "./link_openInNew.css";
23 |
24 | @import "./themes/dark.css";
25 | @import "./themes/light.css";
26 |
27 | /* Шрифты */
28 | @import "../assets/fonts/MaterialSymbols/font.css";
29 |
30 | @font-face {
31 | font-family: "Manrope";
32 | src: url("../assets/fonts/Manrope.ttf") format("truetype");
33 | }
34 |
35 | @font-face {
36 | font-family: "Fira Code";
37 | src: url("../assets/fonts/FiraCode.ttf") format("truetype");
38 | }
39 |
40 | body,
41 | html {
42 | padding: 0;
43 | margin: 0;
44 | background: transparent;
45 | overflow: hidden;
46 | width: 100%;
47 | height: 100%;
48 | border-radius: 32px;
49 | color: var(--text-color);
50 | }
51 |
52 | html.noEffects,
53 | html.noEffects *:not(.icon img):not(.progress-pill):not(.progress-pill *):not(.line-spinner) {
54 | filter: none !important;
55 | box-shadow: none !important;
56 | }
57 |
58 | html.noAnimations,
59 | html.noAnimations *:not(.spinner):not(.progress-pill):not(.progress-pill *):not(.line-spinner) {
60 | transition: 0s !important;
61 | animation-duration: 0.001s !important;
62 | }
63 |
64 | html.noAnimations #aboutItem {
65 | animation: none;
66 | animation-duration: 0s;
67 | }
68 |
69 | * {
70 | transition: 0.2s;
71 | font-family: Manrope, sans-serif;
72 | box-sizing: border-box;
73 | -webkit-user-select: none;
74 | -webkit-app-region: no-drag;
75 | overflow: hidden;
76 | }
77 |
78 | .material-symbols-rounded,
79 | .material-symbols-outlined,
80 | .material-symbols-sharp {
81 | margin: 0;
82 | padding: 0;
83 | height: max-content;
84 | width: max-content;
85 | }
86 |
87 | :root {
88 | --bradius: 8px;
89 | --success: #29ae6b;
90 | --error: #f04438;
91 | --warning: #fdb022;
92 | --info: #53b1fd;
93 | }
94 |
95 | dialog::backdrop {
96 | backdrop-filter: blur(2px);
97 | }
--------------------------------------------------------------------------------
/web/sections/packsAndMods/filters.ejs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/web/modals/servers.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | dns
6 |
{{servers.title}}
7 |
8 |
9 |
10 |
11 |
12 |

13 |
14 |
15 |
22 |
23 |
24 | conversion_path
25 | $3
26 |
27 |
28 | dns
29 | $4
30 |
31 |
32 |
33 | content_copy
35 |
36 |
37 | play_arrow
39 |
40 |
41 |
42 |
43 |
{{servers.disclaimer}}
44 |
45 | open_in_new
46 | {{servers.link}}
47 |
48 |
--------------------------------------------------------------------------------
/web/views/flyout.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
![]()
10 |
{{commons.notSelected}}
11 |
{{flyout.account}}
12 |
13 | chevron_right
14 |
15 |
16 |
17 |
18 |
19 |
21 |
![]()
22 |
{{commons.notSelected}}
23 |
{{flyout.version}}
24 |
25 | chevron_right
26 |
27 |
28 |
29 |
30 |
31 |
32 | {{loading.progress}}
33 | {{loading.progress}}
34 |
35 |
0%
36 |
37 |
38 |
39 |
40 |
{{loading.progress}}
41 |
42 |
43 |
47 |
48 |
49 |
53 |
--------------------------------------------------------------------------------
/web/views/startAnimation.css:
--------------------------------------------------------------------------------
1 | .loading-screen {
2 | position: absolute;
3 | left: 0;
4 | right: 0;
5 | top: 0;
6 | bottom: 0;
7 | z-index: 2;
8 | width: 100vw;
9 | height: 100vh;
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 | flex-direction: column;
14 | gap: 16px;
15 | backdrop-filter: blur(24px);
16 | }
17 |
18 | .loading-screen .caption {
19 | font-weight: 800;
20 | font-size: 48pt;
21 |
22 | overflow: hidden;
23 | white-space: nowrap;
24 | color: white;
25 |
26 | animation-timing-function: ease-in;
27 | animation: fade 1.2s;
28 | }
29 |
30 | .loading-screen .description {
31 | font-weight: 600;
32 | font-size: 16pt;
33 |
34 | overflow: hidden;
35 | white-space: nowrap;
36 | color: white;
37 | margin-top: 16px;
38 |
39 | animation-timing-function: ease-in;
40 | animation: fade 1.2s;
41 | }
42 |
43 | .loading-screen img {
44 | animation-timing-function: ease-in-out;
45 | width: 384px;
46 | transition: 1.2s !important;
47 | clip-path: circle(1%);
48 | }
49 |
50 | .loading-screen img.uncircle {
51 | clip-path: circle(80%);
52 | }
53 |
54 | /* Animate in */
55 | .loading-screen.animateIn .caption {
56 | animation: fadeIn 1.2s;
57 | }
58 |
59 | /* Animate out */
60 | .loading-screen.animateOut img {
61 | animation: zoomFadeOutBig 1.06s;
62 | }
63 |
64 | .loading-screen.animateOut .caption {
65 | animation: fadeOut 1.06s;
66 | }
67 |
68 | .loading-screen.animateOut {
69 | animation: fadeOutLinear 1s;
70 | }
71 |
72 | .hide-on-loading {
73 | display: none !important;
74 | opacity: 0 !important;
75 | }
76 |
77 | @keyframes zoomFadeIn {
78 | 0% {
79 | opacity: 0;
80 | transform: scale(0);
81 | }
82 |
83 | 100% {
84 | opacity: 1;
85 | transform: scale(1);
86 | }
87 | }
88 |
89 | @keyframes zoomFadeOutBig {
90 | 0% {
91 | opacity: 1;
92 | transform: scale(1);
93 | }
94 |
95 | 100% {
96 | opacity: 0;
97 | transform: scale(2.2);
98 | }
99 | }
100 |
101 | @keyframes fadeIn {
102 | 0% {
103 | opacity: 0;
104 | }
105 | 100% {
106 | opacity: 1;
107 | }
108 | }
109 |
110 | @keyframes fadeOut {
111 | 0% {
112 | opacity: 1;
113 | }
114 | 50% {
115 | opacity: 0.01;
116 | }
117 | 100% {
118 | opacity: 0;
119 | }
120 | }
121 |
122 | @keyframes fadeOutLinear {
123 | 0% {
124 | opacity: 1;
125 | }
126 | 100% {
127 | opacity: 0;
128 | }
129 | }
--------------------------------------------------------------------------------
/web/modals/elybyLogin.js:
--------------------------------------------------------------------------------
1 | class FrogElybyLoginUI {
2 | static login = () => {
3 | let login = $("#modal-elybyLogin input.login").val();
4 | let password = $("#modal-elybyLogin input.password").val();
5 | let totp = $("#modal-elybyLogin input.totp").val();
6 |
7 | let $error = $("#modal-elybyLogin .error");
8 |
9 | if (login !== "" && password !== "") {
10 | $("#modal-elybyLogin .loginBtn").hide();
11 | $error.hide();
12 |
13 | FrogElybyManager.loginAccount(login, password, totp).then((data) => {
14 | $("#modal-elybyLogin .loginBtn").show();
15 | $("#modal-elybyLogin input.totp").val("");
16 |
17 | let isSuccess = data[0];
18 | let clientToken = data[2];
19 | data = data[1];
20 |
21 | if (!isSuccess) {
22 | $error.show();
23 | // Показываем ошибку
24 | if (typeof MESSAGES.elyby[data] !== "undefined") {
25 | $error.text(MESSAGES.elyby[data]);
26 | } else {
27 | $error.text(data);
28 | }
29 | return false;
30 | } else {
31 | if (FrogAccountsManager.isAccountExistsByNickname(data.selectedProfile.name, "elyby")) {
32 | FrogToasts.create(MESSAGES.accounts.alreadyExists);
33 | return false;
34 | }
35 |
36 | // Создаём аккаунт
37 | let accountData = {
38 | type: "elyby",
39 | nickname: data.selectedProfile.name,
40 | added: Date.now(),
41 | clientToken: clientToken,
42 | accessToken: data.accessToken,
43 | uuid: data.selectedProfile.id,
44 | }
45 |
46 | let accountsList = FrogAccountsManager.getAccounts();
47 | accountsList[data.selectedProfile.id] = accountData;
48 | FrogElybyManager.getHeadURLByPlayerNickname(data.selectedProfile.name).then(() => {
49 | FrogAccountsManager.saveAccounts(accountsList);
50 | });
51 |
52 | $("#modal-elybyLogin input").val("");
53 | FrogModals.hideModal("elybyLogin").then(() => {
54 | FrogToasts.create(data.selectedProfile.name, "person", MESSAGES.commons.newAccountAdded);
55 | });
56 | return true;
57 | }
58 | });
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/web/modals/versions.css:
--------------------------------------------------------------------------------
1 | #modal-versions {
2 | position: absolute;
3 | z-index: 3;
4 |
5 | padding: 12px;
6 |
7 | width: 56vw;
8 | height: 52vh;
9 |
10 | bottom: calc(12% + 24px);
11 | left: calc(24% + 6px);
12 |
13 | background: var(--theme-primaryBg);
14 |
15 | display: flex;
16 | flex-direction: column;
17 | gap: 12px;
18 | }
19 |
20 | /* Versions list */
21 | #modal-versions .versions-list {
22 | padding: 4px;
23 | flex-grow: 1;
24 | display: flex;
25 | flex-direction: column;
26 | align-items: center;
27 | gap: 8px;
28 | height: max-content;
29 |
30 | background: var(--theme-primaryBg);
31 | border-radius: 8px;
32 | }
33 |
34 | #modal-versions .ver-list-wrapper {
35 | flex-grow: 1;
36 | overflow: auto;
37 | height: 77%;
38 | }
39 |
40 | /* Versions list item */
41 | #modal-versions .versions-list .item {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | gap: 6px;
46 |
47 | height: max-content;
48 | padding: 6px 12px;
49 |
50 | width: 100%;
51 | }
52 |
53 | #modal-versions .versions-list .item .favorite {
54 | cursor: pointer;
55 | }
56 |
57 | #modal-versions .versions-list .item .favorite:hover {
58 | filter: brightness(75%);
59 | }
60 |
61 | #modal-versions .versions-list .item .favorite.active {
62 | color: var(--warning);
63 | }
64 |
65 | #modal-versions .versions-list .item .title {
66 | flex-grow: 1;
67 | }
68 |
69 | #modal-versions .versions-list .item .icon {
70 | position: relative;
71 | display: inline-block;
72 | }
73 |
74 | #modal-versions .versions-list .item .icon {
75 | object-fit: cover;
76 |
77 | width: 44px;
78 | height: 44px;
79 | }
80 |
81 | #modal-versions .versions-list .item .installed {
82 | grid-area: check;
83 | align-self: center;
84 | justify-self: center;
85 | color: var(--success);
86 | }
87 |
88 | #modal-versions .versions-list .item:not([data-installed="true"]) .installed {
89 | display: none !important;
90 | }
91 |
92 | #modal-versions .versions-list .item.active {
93 | outline: 3px solid var(--theme-primary);
94 | }
95 |
96 | #modal-versions .preloader {
97 | width: 100%;
98 | height: 100%;
99 | display: flex;
100 | align-items: center;
101 | justify-content: center;
102 | flex-direction: column;
103 | gap: 12px;
104 | }
105 |
106 | /* Version types selector */
107 | #modal-versions #versionTypeSelect .chip {
108 | cursor: pointer;
109 | }
110 |
111 | #modal-versions #versionTypeSelect .chip.active {
112 | color: var(--theme-text);
113 | background: var(--theme-primary);
114 | }
--------------------------------------------------------------------------------
/web/css/layout.css:
--------------------------------------------------------------------------------
1 | .background {
2 | position: absolute;
3 | left: 0;
4 | right: 0;
5 | top: 0;
6 | bottom: 0;
7 | width: 100vw;
8 | height: 100vh;
9 | z-index: -1000;
10 | }
11 |
12 | .background.img {
13 | background-position: center;
14 | background-repeat: no-repeat;
15 | background-size: cover;
16 | }
17 |
18 | .background.img-1 {
19 | background-image: url("../assets/backgrounds/1.webp");
20 | }
21 |
22 | .background.img-2 {
23 | background-image: url("../assets/backgrounds/2.webp");
24 | }
25 |
26 | .background.img-3 {
27 | background-image: url("../assets/backgrounds/3.webp");
28 | }
29 |
30 | .background.img-4 {
31 | background-image: url("../assets/backgrounds/4.webp");
32 | }
33 |
34 | .background.img-5 {
35 | background-image: url("../assets/backgrounds/5.webp");
36 | }
37 |
38 | .background::after {
39 | content: "";
40 | position: absolute;
41 | width: 100%;
42 | height: 100%;
43 | backdrop-filter: blur(4px) brightness(75%);
44 | pointer-events: none;
45 | }
46 |
47 | body > .layout {
48 | display: grid;
49 | height: 100%;
50 | width: 100%;
51 | padding: 2% 3%;
52 | grid-template-columns: max-content 1fr;
53 | grid-template-rows: max-content 1fr max-content;
54 | gap: 24px 8px;
55 | grid-auto-flow: row;
56 | grid-template-areas:
57 | "title-wrapper title-wrapper"
58 | "flymenu content"
59 | "flyout flyout";
60 | }
61 |
62 | body > .layout > .title-wrapper {
63 | grid-area: title-wrapper;
64 | display: flex;
65 | }
66 |
67 | body > .layout > .title-wrapper .title,
68 | body > .layout > .title-wrapper .title *:not(img) {
69 | -webkit-app-region: drag;
70 | color: white;
71 | }
72 |
73 | body > .layout > .title-wrapper .title {
74 | flex-grow: 1;
75 | }
76 |
77 | body > .layout > .title-wrapper .title span {
78 | font-size: 21pt;
79 | font-weight: 700;
80 | }
81 |
82 | body > .layout .flymenu {
83 | align-self: center;
84 | grid-area: flymenu;
85 | overflow: visible;
86 | }
87 |
88 | body > .layout .content {
89 | grid-area: content;
90 | overflow: visible;
91 | min-height: 73vh;
92 | }
93 |
94 | body > .layout .windowButtons {
95 | justify-self: end;
96 | align-self: center;
97 | grid-area: windowButtons;
98 | }
99 |
100 | body > .layout .flyout {
101 | width: 100%;
102 | justify-self: center;
103 | align-self: center;
104 | grid-area: flyout;
105 | }
106 |
107 | .global-wrapper, .layout, .background {
108 | border-radius: 16px !important;
109 | overflow: hidden;
110 | }
111 |
112 | .global-wrapper {
113 | border: 6px solid var(--theme-primaryBgShadow);
114 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frogLauncher2",
3 | "version": "2.1.14",
4 | "description": "Minecraft-лаунчер. Быстрый. Индивидуальный. Твой",
5 | "main": "app.js",
6 | "scripts": {
7 | "start": "electron .",
8 | "verify": "node ./verifyTranslations.js",
9 | "dev": "cross-env LAUNCHER_IN_DEV=yes electron .",
10 | "build-win": "electron-builder build --win --publish never",
11 | "build-linux": "electron-builder build --linux --publish never",
12 | "test": "cross-env LAUNCHER_IN_DEV=yes LAUNCHER_IN_TEST=yes electron ."
13 | },
14 | "author": "Seeroy",
15 | "license": "GPL-3.0",
16 | "homepage": "https://froglauncher.ru",
17 | "bugs": "https://github.com/Seeroy/FrogLauncher2/issues",
18 | "repository": "https://github.com/Seeroy/FrogLauncher2",
19 | "devDependencies": {
20 | "cross-env": "^7.0.3",
21 | "electron": "^34.2.0",
22 | "electron-builder": "^25.1.8",
23 | "electron-reloader": "^1.2.3"
24 | },
25 | "dependencies": {
26 | "colors": "^1.4.0",
27 | "compressing": "^1.10.1",
28 | "console-stamp": "^3.1.2",
29 | "ejs-electron": "^3.0.0",
30 | "electron-updater": "^6.3.9",
31 | "fs-extra": "^11.3.0",
32 | "gamedig": "^5.2.0",
33 | "jimp": "^0.22.12",
34 | "jquery": "^3.7.1",
35 | "machine-uuid": "^1.2.0",
36 | "mcnbt": "^2.0.3",
37 | "minecraft-launcher-core": "^3.18.2",
38 | "msmc": "^5.0.5",
39 | "open": "^8.4.2",
40 | "request": "^2.88.2",
41 | "tomate-loaders": "^2.0.2",
42 | "toml": "^3.0.0",
43 | "tree-kill": "^1.2.2"
44 | },
45 | "build": {
46 | "productName": "FrogLauncher",
47 | "appId": "ru.seeroy.froglauncher",
48 | "copyright": "© 2024 Seeroy",
49 | "directories": {
50 | "buildResources": "resources"
51 | },
52 | "linux": {
53 | "target": [
54 | "deb",
55 | "rpm",
56 | "tar.gz"
57 | ],
58 | "maintainer": "Seeroy",
59 | "vendor": "Seeroy",
60 | "icon": "web/assets/icon.png"
61 | },
62 | "win": {
63 | "asar": true,
64 | "target": "nsis",
65 | "icon": "resources/icon.ico"
66 | },
67 | "publish": {
68 | "provider": "generic",
69 | "url": "https://cdn.froglauncher.ru/updates"
70 | },
71 | "nsis": {
72 | "installerIcon": "resources/icon.ico",
73 | "installerHeaderIcon": "resources/icon.ico",
74 | "deleteAppDataOnUninstall": false,
75 | "oneClick": false,
76 | "allowToChangeInstallationDirectory": true
77 | },
78 | "files": [
79 | "**/*",
80 | "node_modules/**/*",
81 | "!logs{/*}",
82 | "!logs/",
83 | "!logs",
84 | "!cache{/*}",
85 | "!config.json",
86 | "!frog_accounts.json",
87 | "!LICENSE",
88 | "!README.md",
89 | "!.gitignore",
90 | "!.git{/*}"
91 | ]
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/web/modals/versions.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |

10 |
$1
11 |
star
12 |
13 | download_done
14 |
15 |
16 |
17 |
18 |
19 |
20 |
{{loading.progress}}
21 |
22 |
23 |
24 |
{{versions.releases}}
25 |
{{versions.snapshots}}
26 |
Old Beta
27 |
Old Alpha
28 |
29 | star
30 | {{versions.favourites}}
31 |
32 |
33 | save
34 | {{versions.installed}}
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/web/js/FrogErrorsParser.js:
--------------------------------------------------------------------------------
1 | let isMCErrorShown = false;
2 |
3 | const ERRORS_DESCRIPTIONS = MESSAGES.errors.list;
4 |
5 | const ERRORS_MESSAGES = {
6 | "java.lang.ClassCastException: class jdk.internal.loader":
7 | ERRORS_DESCRIPTIONS[0],
8 | "java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier":
9 | ERRORS_DESCRIPTIONS[1],
10 | "java.lang.UnsupportedClassVersionError": ERRORS_DESCRIPTIONS[0],
11 | "Could not reserve enough space": ERRORS_DESCRIPTIONS[2],
12 | "Main has been compiled by a more recent": ERRORS_DESCRIPTIONS[0],
13 | "The system cannot find the path specified": ERRORS_DESCRIPTIONS[4],
14 | "at java.base/java.io.Reader.": ERRORS_DESCRIPTIONS[5],
15 | "requires version": ERRORS_DESCRIPTIONS[0],
16 | "java.io.IOException: error reading": ERRORS_DESCRIPTIONS[5],
17 | "ava.lang.NoClassDefFoundError: com/mojang/authlib/properties/PropertyMap": ERRORS_DESCRIPTIONS[6],
18 | "Failed to start due to Error: ENOENT: no such file or directory": ERRORS_DESCRIPTIONS[6],
19 | "java.lang.NoSuchMethodError": ERRORS_DESCRIPTIONS[7],
20 | "[authlib-injector] [ERROR] Failed to fetch metadata": ERRORS_DESCRIPTIONS[8]
21 | };
22 |
23 | class FrogErrorsParser {
24 | static parse(line = "", exitCode = 0) {
25 | let errorHappend = false;
26 | if (line === "" && exitCode) {
27 | if (exitCode > 0 && exitCode !== 127 && exitCode !== 255) {
28 | FrogCollector.writeLog(`Crash: Exit code ${exitCode}`);
29 | if (isMCErrorShown === false) {
30 | FrogAlerts.create(
31 | MESSAGES.errors.title,
32 | `${MESSAGES.errors.exitCode}: ${exitCode}`,
33 | MESSAGES.commons.close,
34 | "error",
35 | () => {
36 | isMCErrorShown = false;
37 | }
38 | );
39 | isMCErrorShown = true;
40 | }
41 | } else {
42 | FrogCollector.writeLog(`Crash: Force terminated with code ${exitCode}`);
43 | }
44 | } else {
45 | for (const [key, value] of Object.entries(ERRORS_MESSAGES)) {
46 | let nreg = new RegExp(key, "gmi");
47 | if (line.match(nreg) != null && isMCErrorShown === false) {
48 | isMCErrorShown = true;
49 | FrogAlerts.create(
50 | MESSAGES.errors.title,
51 | value,
52 | MESSAGES.commons.close,
53 | "warning",
54 | () => {
55 | isMCErrorShown = false;
56 | }
57 | );
58 | errorHappend = true;
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/web/modals/frogSkin.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 | add_reaction
9 |
{{frogAuth.skin.title}}
10 |
11 |
14 |
15 |
16 |
{{frogAuth.skin.skin}}
17 |
18 | upload_file
19 |
{{commons.clickOrDrop}}
20 |
21 |
22 |
23 |
24 |
{{frogAuth.skin.cape}}
25 |
26 | upload_file
27 |
{{commons.clickOrDrop}}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/web/modals/accounts.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
9 |
10 |
11 |
12 |
14 |
20 |
$1
21 |
$2
22 |
23 | check
24 |
25 |
26 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
44 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/web/modals/packManager.css:
--------------------------------------------------------------------------------
1 | /* Main window */
2 | #modal-packManager {
3 | width: 94vw;
4 | height: 94vh;
5 | padding: 21px;
6 |
7 | display: flex;
8 | flex-direction: column;
9 | gap: 12px;
10 | }
11 |
12 | #modal-packManager .title-wrapper img.icon {
13 | width: 64px;
14 | height: 64px;
15 | border-radius: 8px;
16 | }
17 |
18 | #modal-packManager .title-wrapper .description {
19 | font-size: 12pt;
20 | color: var(--text-sub);
21 | }
22 |
23 | #modal-packManager .title-wrapper .delete {
24 | background: var(--error);
25 | }
26 |
27 | #modal-packManager .title-wrapper .play {
28 | background: var(--success);
29 | }
30 |
31 | #modal-packManager .title-wrapper .export {
32 | background: var(--info);
33 | }
34 |
35 | #modal-packManager .title-wrapper button.icon {
36 | background: var(--theme-primaryBgLighter);
37 | }
38 |
39 | /* Tabs */
40 | #modal-packManager .tabs .tab {
41 | padding: 12px;
42 | }
43 |
44 | #modal-packManager .tabs button {
45 | padding: 0 10px !important;
46 | height: 40px;
47 | width: max-content;
48 | min-width: max-content;
49 | }
50 |
51 | #modal-packManager .tabs button span {
52 | font-size: 13pt;
53 | }
54 |
55 | /* Layout */
56 | #modal-packManager .layout-tab .scroll-wrap {
57 | padding: 12px;
58 | overflow-y: auto;
59 | max-height: 50vh;
60 | border-radius: 8px;
61 | }
62 |
63 | #modal-packManager .layout-tab .scroll-wrap > div {
64 | display: flex;
65 | flex-direction: column;
66 | width: 100%;
67 | height: max-content;
68 | gap: 8px;
69 | }
70 |
71 | #modal-packManager .layout-tab .scroll-wrap > div .item {
72 | background: var(--theme-primaryBgLight);
73 | padding: 14px;
74 | gap: 4px 14px;
75 | height: max-content;
76 | }
77 |
78 | #modal-packManager .layout-tab .scroll-wrap > div .item .description {
79 | color: var(--text-sub);
80 | opacity: 0.9;
81 | text-transform: none;
82 | font-size: 13pt;
83 | font-weight: 300;
84 | }
85 |
86 | #modal-packManager .layout-tab:not(.active) {
87 | display: none;
88 | }
89 |
90 | /* Item */
91 | #modal-packManager .layout-tab .custom-select.icon-and-description:hover {
92 | filter: none;
93 | cursor: default;
94 | }
95 |
96 | #modal-packManager .layout-tab .custom-select.icon-and-description button:hover {
97 | filter: brightness(115%);
98 | }
99 |
100 | #modal-packManager .layout-tab .custom-select.icon-and-description {
101 | grid-template-columns: max-content 40px 1fr max-content;
102 | grid-template-areas:
103 | "switch icon title button"
104 | "switch icon description button";
105 | }
106 |
107 | #modal-packManager .layout-tab .custom-select.icon-and-description .switch {
108 | justify-self: center;
109 | align-self: center;
110 | grid-area: switch;
111 |
112 | width: 48px;
113 | height: 28px;
114 | }
115 |
116 | #modal-packManager .layout-tab .custom-select.icon-and-description input:not(:checked) + .inner:before {
117 | left: 0;
118 | }
119 |
120 | #modal-packManager .layout-tab .custom-select.icon-and-description .switch .inner:before {
121 | height: 21px;
122 | width: 21px;
123 | left: -8px;
124 | bottom: 3px;
125 | }
--------------------------------------------------------------------------------
/web/modals/servers.css:
--------------------------------------------------------------------------------
1 | #modal-servers {
2 | display: flex;
3 | flex-direction: column;
4 | gap: 6px;
5 | }
6 |
7 | #modal-servers .serversListWrapper {
8 | width: 100%;
9 | height: 80%;
10 | overflow: auto;
11 |
12 | display: flex;
13 | justify-content: center;
14 | flex-grow: 1;
15 | }
16 |
17 | #modal-servers .serversList {
18 | display: flex;
19 | flex-direction: column;
20 | width: 70%;
21 | height: max-content;
22 |
23 | align-items: center;
24 | justify-content: center;
25 | gap: 8px;
26 | }
27 |
28 | #modal-servers .serversList .item {
29 | background: var(--theme-primaryBgLight);
30 | border-radius: 16px;
31 |
32 | padding: 6px 20px;
33 |
34 | width: 100%;
35 | height: max-content;
36 | display: flex;
37 | align-items: center;
38 | justify-content: center;
39 | gap: 16px;
40 | }
41 |
42 | #modal-servers .serversList .item .caption {
43 | font-weight: 700;
44 | font-size: 16pt;
45 | color: var(--text-color);
46 | }
47 |
48 | #modal-servers .serversList .item .description {
49 | font-weight: 500;
50 | font-size: 11pt;
51 | color: var(--text-sub);
52 | }
53 |
54 | /* Кнопки и чипы */
55 | #modal-servers .serversList .item .ip,
56 | #modal-servers .serversList .item .version {
57 | width: max-content;
58 | height: 32px;
59 | background: var(--theme-primaryBgLighter);
60 | padding: 6px;
61 | border-radius: 4px;
62 | display: flex;
63 | align-items: center;
64 | justify-content: center;
65 | margin: 4px 0;
66 |
67 | gap: 8px;
68 | }
69 |
70 | #modal-servers .serversList .item .ip *,
71 | #modal-servers .serversList .item .version * {
72 | font-size: 11pt;
73 | color: var(--text-sub);
74 | }
75 |
76 | #modal-servers .serversList .item .copy-btn,
77 | #modal-servers .serversList .item .play-btn {
78 | margin: 0 8px;
79 | }
80 |
81 | #modal-servers .serversList .item .copy-btn .material-symbols-outlined,
82 | #modal-servers .serversList .item .play-btn .material-symbols-outlined {
83 | font-size: 21pt;
84 | color: var(--theme-primaryLighter);
85 | cursor: pointer;
86 | }
87 |
88 | #modal-servers .serversList .item .copy-btn .material-symbols-outlined:hover,
89 | #modal-servers .serversList .item .play-btn .material-symbols-outlined:hover {
90 | color: var(--text-sub);
91 | }
92 |
93 | #modal-servers .disclaimer {
94 | color: var(--text-sub);
95 | font-size: 11pt;
96 | text-align: center;
97 | }
98 |
99 | /* Иконка со статусом */
100 | #modal-servers .serversList .item .icon {
101 | position: relative;
102 | display: inline-block;
103 | }
104 |
105 | #modal-servers .serversList .item .icon img {
106 | object-fit: cover;
107 | }
108 |
109 | #modal-servers .serversList .item .icon .status {
110 | border: 2px solid var(--theme-primaryBgLight);
111 | position: absolute;
112 | bottom: 2%;
113 | right: -1%;
114 | border-radius: 99999px;
115 |
116 | width: 18px;
117 | height: 18px;
118 |
119 | background: var(--warning);
120 | }
121 |
122 | #modal-servers .serversList .item .icon .status.offline {
123 | background: var(--error);
124 | }
125 |
126 | #modal-servers .serversList .item .icon .status.online {
127 | background: var(--success);
128 | }
--------------------------------------------------------------------------------
/web/js/renderer.js:
--------------------------------------------------------------------------------
1 | window.$ = window.jQuery = require("jquery");
2 | const fs = require("fs");
3 | const fsPromises = require("fs/promises");
4 | const fsExtra = require("fs-extra");
5 | const path = require("path");
6 | const os = require("os");
7 | const {ipcRenderer} = require("electron");
8 | const crypto = require("crypto");
9 | const openExternal = require("open");
10 | const request = require("request");
11 | const {Client, Authenticator} = require("minecraft-launcher-core");
12 | const {Auth} = require("msmc");
13 | const {forge, neoforge, fabric, quilt, vanilla, liner} = require("tomate-loaders");
14 | const machineUuid = require("machine-uuid");
15 | const packageJson = require("./../package.json");
16 | const Jimp = require("jimp");
17 | const compressing = require('compressing');
18 | const treeKill = require("tree-kill");
19 | const NBT = require('mcnbt');
20 | const {exec} = require('node:child_process');
21 | let toml = require('toml');
22 | const {GameDig} = require('gamedig');
23 |
24 | // Версия лаунчера
25 | global.LAUNCHER_VERSION = packageJson.version;
26 |
27 | // Ссылки на сайт
28 | global.SITE_URL = "https://froglauncher.ru";
29 | global.CDN_URL = "https://cdn.froglauncher.ru";
30 | global.SKINS_API_URL = "https://skins.froglauncher.ru";
31 | global.STATS_URL = "https://statscol.seeeroy.ru/save_fl?savedata=";
32 | //global.NEWS_URL = CDN_URL + "/news.json"; // Перемещено в index.ejs
33 | global.SERVERS_URL = CDN_URL + `/servers.json?_=${Date.now()}`;
34 | global.AUTHLIB_INJECTOR_URL = CDN_URL + "/authlib-injector.jar";
35 | global.REPO_NAME = "Seeroy/FrogLauncher2";
36 |
37 | // Ссылка на Java
38 | global.JAVA_LIST_URL = "https://api.adoptium.net/v3/info/available_releases";
39 |
40 | // Последние логи лаунчера
41 | global.LAST_LOG = "";
42 |
43 | // Лаунчер в dev mode? test mode?
44 | global.IS_APP_IN_DEV = ipcRenderer.sendSync("isAppInDev");
45 | global.IS_APP_IN_TEST = ipcRenderer.sendSync("isAppInTest");
46 |
47 | if (IS_APP_IN_DEV) {
48 | //global.SKINS_API_URL = "http://localhost:58883";
49 | }
50 |
51 | // Пути к файлам
52 | global.USERDATA_PATH = path.normalize(ipcRenderer.sendSync("get-userdata-path"));
53 | global.CONFIG_PATH = path.join(global.USERDATA_PATH, "config.json");
54 | if (process.platform === "win32") {
55 | global.DOT_MC_PATH = path.join(os.homedir(), "AppData", "Roaming", ".minecraft");
56 | } else {
57 | global.DOT_MC_PATH = path.join(os.homedir(), ".minecraft");
58 | }
59 |
60 | const animateCSS = (element, animation, fast = true, prefix = "animate__") => {
61 | const animationName = `${prefix}${animation}`;
62 | const node = document.querySelector(element);
63 |
64 | return animateCSSNode(node);
65 | };
66 |
67 | const animateCSSNode = (node, animation, fast = true, prefix = "animate__") => {
68 | return new Promise((resolve, reject) => {
69 | const animationName = `${prefix}${animation}`;
70 |
71 | if (fast === true) {
72 | node.classList.add(`${prefix}animated`, animationName, `${prefix}faster`);
73 | } else {
74 | node.classList.add(`${prefix}animated`, animationName);
75 | }
76 |
77 | function handleAnimationEnd(event) {
78 | event.stopPropagation();
79 | node.classList.remove(
80 | `${prefix}animated`,
81 | animationName,
82 | `${prefix}faster`
83 | );
84 | resolve("Animation ended");
85 | }
86 |
87 | node.addEventListener("animationend", handleAnimationEnd, {once: true});
88 | });
89 | }
--------------------------------------------------------------------------------
/web/js/FrogServersUI.js:
--------------------------------------------------------------------------------
1 | class FrogServersUI {
2 | // Загрузить список серверов
3 | static loadList = async () => {
4 | let [isSuccess, servers] = await FrogRequests.get(SERVERS_URL);
5 | if (!isSuccess) {
6 | return false;
7 | }
8 |
9 | // Очищаем список
10 | $("#modal-servers .serversList .item:not(.placeholder)").remove();
11 |
12 | // Получаем код placeholder`а
13 | let placeholder = $("#modal-servers .serversList .item.placeholder")[0].outerHTML;
14 | placeholder = placeholder.replace(' placeholder', "");
15 | // По placeholder`у добавляем новые элементы
16 | servers.servers.forEach((srv) => {
17 | let preparedPlaceholder = placeholder.replaceAll(/\$1/gim, srv.name)
18 | .replaceAll(/\$2/gim, srv.description)
19 | .replaceAll(/\$3/gim, srv.version)
20 | .replaceAll(/\$4/gim, srv.ip)
21 | .replaceAll(/\$5/gim, srv.icon)
22 | .replaceAll(/\$6/gim, srv.ip + ":" + srv.port)
23 | .replaceAll(/\$7/gim, srv.flVersion);
24 | $("#modal-servers .serversList").append(preparedPlaceholder);
25 | })
26 |
27 | // Показываем все в списке
28 | $("#modal-servers .serversList .item").each(function () {
29 | if (!$(this).hasClass("placeholder")) {
30 | $(this).show();
31 | }
32 | })
33 | FrogServersUI.refreshServersInfo();
34 | return true;
35 | }
36 |
37 | // Скопировать IP сервера
38 | static copyServerIP(ip) {
39 | navigator.clipboard.writeText(ip);
40 | FrogToasts.create(MESSAGES.servers.ipCopied, "share");
41 | }
42 |
43 | // Получить статус и информацию о сервере
44 | static queryServer = async (ip) => {
45 | let port = 25565;
46 | if(ip.split(":").length === 2){
47 | port = ip.split(":")[1];
48 | ip = ip.split(":")[0];
49 | }
50 | try {
51 | let query = await GameDig.query({type: "minecraft", host: ip, port: port});
52 | return {
53 | online: true,
54 | data: query
55 | }
56 | } catch(e) {
57 | return {
58 | online: false
59 | };
60 | }
61 | }
62 |
63 | // Обновить информацию о серверах в списке
64 | static refreshServersInfo = () => {
65 | $("#modal-servers .serversList .item:not(.placeholder)").each(async function () {
66 | let $status = $(this).find("div.status");
67 | let $onlineChip = $(this).find("div.cap-div div.chip");
68 | let ip = $(this).data("ip");
69 |
70 | $status.removeClass("online offline");
71 | $onlineChip.text("...");
72 |
73 | let serverQuery = await FrogServersUI.queryServer(ip);
74 | if(!serverQuery.online){
75 | $status.addClass("offline");
76 | $onlineChip.hide();
77 | } else {
78 | $status.addClass("online");
79 | $onlineChip.show();
80 | $onlineChip.text(`${serverQuery.data.numplayers} / ${serverQuery.data.maxplayers}`);
81 | }
82 | })
83 | }
84 |
85 | // Играть на сервере
86 | static playOnServer = async (ip, versionId) => {
87 | selectedServerFromList = ip;
88 | FrogVersionsManager.setActiveVersion(versionId);
89 | FrogFlyout.startSelectedVersion();
90 | return true;
91 | }
92 | }
--------------------------------------------------------------------------------
/web/js/colors.js:
--------------------------------------------------------------------------------
1 | class colors {
2 | // Цвет светлый или тёмный (для цвета текста)
3 | static isColorLight = (color) => {
4 | const rgb = [
5 | parseInt(color.substring(1, 3), 16),
6 | parseInt(color.substring(3, 5), 16),
7 | parseInt(color.substring(5), 16),
8 | ];
9 | const luminance =
10 | (0.2126 * rgb[0]) / 255 +
11 | (0.7152 * rgb[1]) / 255 +
12 | (0.0722 * rgb[2]) / 255;
13 | return luminance > 7;
14 | }
15 |
16 | // Цифра цвета RGB в двузначный HEX
17 | static componentToHex = (c) => {
18 | let hex = c.toString(16);
19 | return hex.length === 1 ? "0" + hex : hex;
20 | }
21 |
22 | // rgb() to #hex
23 | static rgbToHex = (r, g, b) => {
24 | return "#" + colors.componentToHex(r) + colors.componentToHex(g) + colors.componentToHex(b);
25 | }
26 |
27 | // rgb() to hsl()
28 | static RGBToHSL = (r, g, b) => {
29 | // Make r, g, and b fractions of 1
30 | r /= 255;
31 | g /= 255;
32 | b /= 255;
33 |
34 | // Find greatest and smallest channel values
35 | let cmin = Math.min(r, g, b),
36 | cmax = Math.max(r, g, b),
37 | delta = cmax - cmin,
38 | h = 0,
39 | s = 0,
40 | l = 0;
41 |
42 | if (delta === 0)
43 | h = 0;
44 | // Red is max
45 | else if (cmax === r)
46 | h = ((g - b) / delta) % 6;
47 | // Green is max
48 | else if (cmax === g)
49 | h = (b - r) / delta + 2;
50 | // Blue is max
51 | else
52 | h = (r - g) / delta + 4;
53 |
54 | h = Math.round(h * 60);
55 |
56 | // Make negative hues positive behind 360°
57 | if (h < 0)
58 | h += 360;
59 |
60 | // Calculate lightness
61 | l = (cmax + cmin) / 2;
62 |
63 | // Calculate saturation
64 | s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
65 |
66 | // Multiply l and s by 100
67 | s = +(s * 100).toFixed(0);
68 | l = +(l * 100).toFixed(0);
69 |
70 | return {
71 | h: h,
72 | s: s,
73 | l: l
74 | };
75 | }
76 |
77 | // Вычесть значение из цвета RGB
78 | static rgbMinus = (color, minus) => {
79 | let matched = color.match(colorsMatchRegex);
80 | if (matched === null || matched.length !== 4) {
81 | return false;
82 | }
83 |
84 | let r = matched[1];
85 | let g = matched[2];
86 | let b = matched[3];
87 |
88 | r = parseInt(r) + parseInt(minus);
89 | g = parseInt(g) + parseInt(minus);
90 | b = parseInt(b) + parseInt(minus);
91 |
92 | if (r > 255) {
93 | r = 255;
94 | } else if (r < 0) {
95 | r = 0;
96 | }
97 | if (g > 255) {
98 | g = 255;
99 | } else if (g < 0) {
100 | g = 0;
101 | }
102 | if (b > 255) {
103 | b = 255;
104 | } else if (b < 0) {
105 | b = 0;
106 | }
107 | return "rgb(" + r + ", " + g + ", " + b + ")";
108 | }
109 |
110 | // #hex to rgb()
111 | static hexToRgb = (hex) => {
112 | hex = hex.toLowerCase();
113 | let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
114 | return result ? {
115 | r: parseInt(result[1], 16),
116 | g: parseInt(result[2], 16),
117 | b: parseInt(result[3], 16)
118 | } : null;
119 | }
120 |
121 | }
--------------------------------------------------------------------------------
/web/modals/accounts.css:
--------------------------------------------------------------------------------
1 | #modal-accounts {
2 | position: absolute;
3 | z-index: 2;
4 |
5 | padding: 12px;
6 |
7 | width: 25vw;
8 | height: max-content;
9 | min-height: 50vh;
10 | max-height: 80vh;
11 |
12 | bottom: calc(12% + 24px);
13 | left: calc(3% + 6px);
14 |
15 | background: var(--theme-primaryBg);
16 |
17 | display: flex;
18 | flex-direction: column;
19 | gap: 12px;
20 | }
21 |
22 | #modal-accounts .acc-btns {
23 | display: flex;
24 | gap: 6px;
25 | }
26 |
27 | #modal-accounts .acc-btns button,
28 | #modal-accounts .addFrog {
29 | width: 100%;
30 | padding: 0;
31 | font-size: 14pt;
32 | height: 40px;
33 | }
34 |
35 | /* Accounts list */
36 | #modal-accounts .accounts-list {
37 | padding: 4px;
38 | flex-grow: 1;
39 | display: flex;
40 | flex-direction: column;
41 | align-items: center;
42 | gap: 8px;
43 | height: max-content;
44 |
45 | background: var(--theme-primaryBg);
46 | border-radius: 8px;
47 | }
48 |
49 | #modal-accounts .acc-list-wrapper {
50 | flex-grow: 1;
51 | overflow: auto;
52 | height: max-content;
53 | }
54 |
55 | /* Accounts list item */
56 | #modal-accounts .accounts-list .item {
57 | grid-template-columns: max-content 1fr max-content max-content;
58 | grid-template-rows: max-content max-content;
59 | gap: 6px;
60 | grid-template-areas:
61 | "icon title buttons check"
62 | "icon description buttons check";
63 |
64 | height: max-content;
65 | padding: 6px 12px;
66 |
67 | width: 100%;
68 | }
69 |
70 | #modal-accounts .accounts-list .item .buttons {
71 | grid-area: buttons;
72 | align-self: center;
73 | justify-self: center;
74 |
75 | display: flex;
76 | align-items: center;
77 | justify-content: center;
78 | gap: 2px;
79 | }
80 |
81 | #modal-accounts .accounts-list .item .icon,
82 | #accountSelect .icon {
83 | position: relative;
84 | display: inline-block;
85 | }
86 |
87 | #modal-accounts .accounts-list .item .icon .avatar,
88 | #accountSelect .icon .avatar {
89 | object-fit: cover;
90 |
91 | width: 40px;
92 | height: 40px;
93 | }
94 |
95 | #modal-accounts .accounts-list .item .icon .type,
96 | #accountSelect .icon .type {
97 | border: 2px solid var(--theme-primaryBgLight);
98 | position: absolute;
99 | bottom: -6%;
100 | right: -4%;
101 | border-radius: 4px;
102 |
103 | width: 18px;
104 | height: 18px;
105 |
106 | background: var(--theme-primaryBgLight) !important;
107 | }
108 |
109 | #modal-accounts .accounts-list .item .check {
110 | grid-area: check;
111 | align-self: center;
112 | justify-self: center;
113 | color: var(--theme-primary);
114 | }
115 |
116 | #modal-accounts .accounts-list .item:not(.active) .check {
117 | display: none;
118 | }
119 |
120 | #modal-accounts .accounts-list .item .buttons button {
121 | cursor: pointer;
122 |
123 | width: 32px;
124 | height: 32px;
125 | padding: 0;
126 | display: none;
127 | }
128 |
129 | #modal-accounts .accounts-list .item .delete:hover * {
130 | color: var(--error);
131 | }
132 |
133 | #modal-accounts .accounts-list .item .edit:hover * {
134 | color: var(--theme-primary);
135 | }
136 |
137 | #modal-accounts .accounts-list .item:not(.active) .delete {
138 | display: inherit;
139 | }
140 |
141 | #modal-accounts .accounts-list .item.active {
142 | outline: 3px solid var(--theme-primary);
143 | }
144 |
145 | /* Add local item */
146 | #modal-accounts .add-local {
147 | background: transparent;
148 | display: flex;
149 | align-items: center;
150 | justify-content: center;
151 | width: 100%;
152 | height: max-content;
153 | padding: 6px;
154 | border-radius: 8px;
155 | gap: 4px;
156 | clip-path: inset(0 0 0 0);
157 | transition: clip-path 1s;
158 | }
159 |
160 | #modal-accounts .add-local.hidden {
161 | clip-path: inset(0 0 99px 0);
162 | }
163 |
164 | #modal-accounts .add-local input {
165 | flex-grow: 1;
166 | }
--------------------------------------------------------------------------------
/web/js/FrogModals.js:
--------------------------------------------------------------------------------
1 | class FrogModals {
2 | // Показать модальное окно
3 | static showModal = async (modalName) => {
4 | FrogCollector.writeLog(`Modal: Show "${modalName}"`);
5 |
6 | document.dispatchEvent(new CustomEvent("showModalEvent", {
7 | detail: {modal: modalName}
8 | }));
9 | if (FrogModals.isModalShown(modalName)) {
10 | return false;
11 | }
12 | let modalElem = $(`.modal#modal-${modalName}`);
13 | modalElem.show();
14 | if (modalElem[0].tagName === "DIALOG") {
15 | modalElem[0].showModal();
16 | }
17 | await animateCSSNode(modalElem[0], "fadeInUp");
18 | return true;
19 | }
20 |
21 | // Скрыть модальное окно
22 | static hideModal = async (modalName) => {
23 | FrogCollector.writeLog(`Modal: Hide "${modalName}"`);
24 |
25 | document.dispatchEvent(new CustomEvent("hideModalEvent", {
26 | detail: {modal: modalName}
27 | }));
28 | if (!FrogModals.isModalShown(modalName)) {
29 | return false;
30 | }
31 | let modalElem = $(`.modal#modal-${modalName}`);
32 | await animateCSSNode(modalElem[0], "fadeOutDown");
33 | modalElem.hide();
34 | if (modalElem[0].tagName === "DIALOG") {
35 | modalElem[0].close();
36 | }
37 | return true;
38 | }
39 |
40 | // Переключить окно (если сейчас показано какое-либо)
41 | static switchModal = async (modalName) => {
42 | FrogFlyout.lockFlymenu();
43 | $(`.flymenu .item[data-modal="${FrogModals.currentModalName()}"]`).removeClass("active");
44 | $(`.flymenu .item[data-modal="${modalName}"]`).addClass("active");
45 | await FrogModals.hideCurrentModal();
46 | await FrogModals.showModal(modalName);
47 | FrogFlyout.unlockFlymenu();
48 | return true;
49 | }
50 |
51 | // Скрыть модальное окно, которое сейчас открыто
52 | static hideCurrentModal = async () => {
53 | let currentModalElement = $(`.modal[style!="display: none;"]:not(.overlay)`);
54 | let currentModalName = $(currentModalElement).attr("id").replace("modal-", "");
55 | await FrogModals.hideModal(currentModalName);
56 | return true;
57 | }
58 |
59 | // Скрыть модальное overlay окно, которое сейчас открыто
60 | static hideCurrentOverlay = async () => {
61 | let currentModalElement = $(`.modal.overlay[style!="display: none;"]`);
62 | if (currentModalElement.length === 0) return false;
63 | let currentModalName = $(currentModalElement).attr("id").replace("modal-", "");
64 | await FrogModals.hideModal(currentModalName);
65 | return true;
66 | }
67 |
68 | // Получить название modal на переднем плане
69 | static currentModalName = () => {
70 | let $currentModal = $(`.modal[style!="display: none;"]:not(.overlay)`);
71 | return $currentModal[0].id.replace("modal-", "");
72 | }
73 |
74 | // Показано ли модальное окно (если "", то любое из окон)
75 | static isModalShown = (modalName = "") => {
76 | if (modalName !== "") {
77 | return $(`.modal#modal-${modalName}`).css("display") !== "none";
78 | } else {
79 | return $(`.modal[style!="display: none;"]:not(.overlay)`).length > 0;
80 | }
81 | }
82 |
83 | // Показано ли overlay модальное окно (если "", то любое из окон)
84 | static isOverlayModalShown = (modalName = "") => {
85 | if (modalName !== "") {
86 | return FrogModals.isModalShown(modalName);
87 | } else {
88 | return $(`.modal.overlay[style!="display: none;"]`).length > 0;
89 | }
90 | }
91 |
92 | // Скрыть все и переключиться на контент
93 | static switchToContent = () => {
94 | $(".modal.overlay").each(function () {
95 | FrogModals.hideModal($(this)[0].id.replace("modal-", ""));
96 | })
97 | if ($(`.modal[style!="display: none;"]:not(.overlay)`)[0].id !== "modal-content") {
98 | FrogModals.switchModal("content");
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/PRIVACY_POLICY.md:
--------------------------------------------------------------------------------
1 | # Политика конфиденциальности FrogLauncher
2 |
3 | **Последнее обновление:** 25 февраля 2025 года
4 |
5 | Настоящая Политика конфиденциальности (далее — «Политика») описывает, как разработчик программного обеспечения FrogLauncher, seeeroy (далее — «Разработчик»), собирает, использует, хранит и защищает персональные данные пользователей (далее — «Пользователь»).
6 |
7 | ---
8 |
9 | ### **1. Типы собираемых данных**
10 | 1.1. При каждом запуске Лаунчера автоматически собираются следующие данные:
11 | - Название операционной системы (ОС);
12 | - Название процессора;
13 | - Количество оперативной памяти;
14 | - Уникальный идентификатор пользователя (ID), генерируемый с использованием библиотеки [machine-uuid](https://www.npmjs.com/package/machine-uuid);
15 | - Версия Лаунчера;
16 | - Установленные самим Лаунчером версии Java;
17 | - Количество созданных сборок;
18 | - Количество установленных версий;
19 | - Версии используемого в Лаунчере программного обеспечения.
20 |
21 | ---
22 |
23 | ### **2. Методы сбора данных**
24 | 2.1. Сбор данных осуществляется Лаунчером при его запуске путем отправки данных на сервер по защищенному протоколу HTTPS.
25 |
26 | ---
27 |
28 | ### **3. Использование данных**
29 | 3.1. Собранные данные используются для следующих целей:
30 | - Диагностика проблем пользователей: уникальный идентификатор (ID) позволяет связать данные об аппаратном обеспечении, версии Лаунчера и Java с конкретным пользователем для оказания технической поддержки;
31 | - Получение обратной связи от Пользователей;
32 | - Улучшение общего пользовательского опыта;
33 | - Исправление возможных ошибок и багов в Лаунчере.
34 |
35 | 3.2. Данные не передаются третьим лицам и используются исключительно Разработчиком для указанных выше целей.
36 |
37 | ---
38 |
39 | ### **4. Правовые основания обработки данных**
40 | 4.1. Обработка данных осуществляется на основании:
41 | - Согласия Пользователя, предоставляемого при первом запуске Лаунчера;
42 | - Законного интереса Разработчика в улучшении работы Лаунчера и оказании технической поддержки.
43 |
44 | ---
45 |
46 | ### **5. Права пользователей**
47 | 5.1. Пользователь имеет следующие права в отношении своих данных:
48 | - Право на доступ к данным, отправляемым Лаунчером;
49 | - Право на исправление неточных данных;
50 | - Право на удаление своих данных;
51 | - Право на ограничение обработки данных;
52 | - Право на отзыв согласия на обработку данных.
53 |
54 | 5.2. Для реализации своих прав Пользователь может направить запрос на любые из контактов Разработчика, расположенные на его официальном сайте.
55 |
56 | ---
57 |
58 | ### **6. Хранение и защита данных**
59 | 6.1. Разработчик принимает все необходимые меры для защиты данных Пользователей от несанкционированного доступа, изменения, раскрытия или уничтожения.
60 |
61 | 6.2. Данные хранятся на серверах, подконтрольных Разработчику, и защищены с использованием современных средств защиты информации.
62 |
63 | 6.3. Срок хранения данных составляет **3 года**. По истечении этого периода данные будут удалены.
64 |
65 | ---
66 |
67 | ### **7. Международная передача данных**
68 | 7.1. Данные Пользователей хранятся и обрабатываются на территории страны, где находится Разработчик. Международная передача данных не осуществляется.
69 |
70 | ---
71 |
72 | ### **8. Изменения в Политике**
73 | 8.1. Разработчик оставляет за собой право вносить изменения в настоящую Политику в одностороннем порядке. Изменения вступают в силу с момента их публикации на официальном сайте Разработчика.
74 |
75 | 8.2. Пользователи будут уведомлены об изменениях через новости в Лаунчере.
76 |
77 | 8.3. Продолжение использования Лаунчера после внесения изменений в Политику считается согласием Пользователя с новыми условиями.
78 |
79 | ---
80 |
81 | ### **9. Согласие с Политикой**
82 | 9.1. При первом запуске Лаунчера Пользователь будет уведомлен о необходимости ознакомиться с настоящей Политикой и дать согласие на обработку данных. Согласие предоставляется путем нажатия кнопки «Согласен» во всплывающем окне.
83 |
84 | 9.2. Пользователь может отозвать свое согласие в любой момент, направив запрос на контакты Разработчика, и приостановив использование продукта.
85 |
86 | ---
87 |
88 | ### **10. Контактная информация**
89 | 10.1. По всем вопросам, связанным с обработкой данных, Пользователь может обратиться к Разработчику через контактные данные, указанные на официальном сайте.
90 |
91 | Используя FrogLauncher, вы подтверждаете, что ознакомились с условиями данной Политики конфиденциальности и согласны с ними.
--------------------------------------------------------------------------------
/web/modals/settings.css:
--------------------------------------------------------------------------------
1 | #modal-settings .settings-layout > h3 {
2 | font-weight: 600;
3 | margin: 21px 0;
4 | padding: 0;
5 | }
6 |
7 | #modal-settings .content {
8 | display: flex;
9 | gap: 16px;
10 | }
11 |
12 | #modal-settings .content .tabs {
13 | width: 26vw;
14 | }
15 |
16 | #modal-settings .content .tabs .tab {
17 | height: 54px;
18 | }
19 |
20 | #modal-settings .settings-layout .splitter {
21 | margin: 8px 0;
22 | width: 100%;
23 | height: 2px;
24 | background: rgba(190, 190, 190, 0.25);
25 | }
26 |
27 | #modal-settings .settings-item {
28 | background: var(--theme-primaryBgLight);
29 | padding: 12px;
30 | border-radius: 8px;
31 | }
32 |
33 | #modal-settings > h2 {
34 | font-weight: 400;
35 | margin: 16px 0;
36 | }
37 |
38 | #modal-settings .settings-item .name {
39 | font-weight: 600;
40 | font-size: 14pt;
41 | }
42 |
43 | #modal-settings .settings-item .description {
44 | font-size: 11pt;
45 | font-weight: 400;
46 | color: var(--text-sub);
47 | max-width: 40vw;
48 | }
49 |
50 | #modal-settings .settings-item .icon {
51 | background: var(--theme-primaryBgLighter);
52 | width: 44px;
53 | height: 44px;
54 | aspect-ratio: 1/1;
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 | border-radius: 8px;
59 | margin-right: 6px;
60 | }
61 |
62 | #modal-settings .settings-item .icon img {
63 | width: 26px;
64 | height: 26px;
65 | }
66 |
67 | html.dark #modal-settings .settings-item .icon img {
68 | filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(184deg) brightness(103%) contrast(101%);
69 | }
70 |
71 | html.light #modal-settings .settings-item img.icon {
72 | filter: none;
73 | }
74 |
75 | #modal-settings .settings-item .icon * {
76 | color: var(--text-color);
77 | }
78 |
79 | #modal-settings .settings-layout {
80 | overflow-x: hidden;
81 | overflow-y: auto;
82 | height: 60vh;
83 | width: 100%;
84 | padding-right: 8px;
85 | }
86 |
87 | /* Micro placeholder for dark mode switch */
88 | #modal-settings .placeholder.active,
89 | #modal-settings .settings-item .wp-item.active {
90 | border: 4px solid var(--theme-primary);
91 | }
92 |
93 | #modal-settings .placeholder:not(.active):hover,
94 | #modal-settings .settings-item .wp-item:not(.active):hover {
95 | cursor: pointer;
96 | filter: brightness(120%);
97 | }
98 |
99 | #modal-settings .placeholder {
100 | width: 96px;
101 | height: 64px;
102 | display: grid;
103 | grid-template-columns: 12px 1fr 24px;
104 | grid-template-rows: 1fr 1fr 8px;
105 | gap: 4px 4px;
106 | grid-auto-flow: row;
107 | grid-template-areas:
108 | "left main main"
109 | "left main main"
110 | "bottom-1 bottom-1 bottom-2";
111 | padding: 6px;
112 | border-radius: 8px;
113 | background: #3c3c3c;
114 | }
115 |
116 | #modal-settings .placeholder.light {
117 | background: #f1f1f1;
118 | }
119 |
120 | #modal-settings .placeholder .left {
121 | border-radius: 4px;
122 | grid-area: left;
123 | background: #bdbdbd;
124 | }
125 |
126 | #modal-settings .placeholder.light .left {
127 | background: #606060;
128 | }
129 |
130 | #modal-settings .placeholder .bottom-1 {
131 | grid-area: bottom-1;
132 | background: #818181;
133 | border-radius: 4px;
134 | }
135 |
136 | #modal-settings .placeholder.light .bottom-1 {
137 | background: #757575;
138 | }
139 |
140 | #modal-settings .placeholder .bottom-2 {
141 | grid-area: bottom-2;
142 | background: #0a73ff;
143 | border-radius: 4px;
144 | }
145 |
146 | #modal-settings .placeholder .main {
147 | grid-area: main;
148 | background: #818181;
149 | border-radius: 4px;
150 | }
151 |
152 | /* Wallpapers changer */
153 | #modal-settings .settings-item .wp-item {
154 | height: 50px;
155 | border-radius: 6px;
156 | }
157 |
158 | #modal-settings .settings-item .wp-item.placeholder {
159 | display: flex;
160 | align-items: center;
161 | justify-content: center;
162 | background: var(--theme-primaryBgLighter);
163 | width: 78px;
164 | }
165 |
166 | #modal-settings .settings-item .wp-item.placeholder * {
167 | color: var(--text-color);
168 | }
169 |
170 | /* Tabs */
171 | #modal-settings .layout-tab {
172 | display: flex;
173 | flex-direction: column;
174 | gap: 16px 0;
175 | }
176 |
177 | #modal-settings .layout-tab:not(.active) {
178 | display: none;
179 | }
180 |
181 | #modal-settings .packs-layout {
182 | display: flex;
183 | flex-direction: column;
184 | gap: 8px;
185 | }
--------------------------------------------------------------------------------
/EULA.md:
--------------------------------------------------------------------------------
1 | # Пользовательское соглашение FrogLauncher
2 |
3 | Последнее обновление: 25 июля 2024 года
4 |
5 | Настоящее
6 | Пользовательское соглашение (далее — «Соглашение») регулирует отношения между разработчиком программного
7 | обеспечения FrogLauncher, seeeroy (далее — «Разработчик»), и пользователем данного программного обеспечения
8 | (далее — «Пользователь»). Используя FrogLauncher, Пользователь соглашается с условиями данного Соглашения.
9 |
10 | ### 1. Общие положения
11 | 1.1. FrogLauncher (далее — «Лаунчер») предназначен для запуска игры Minecraft с
12 | использованием официальных аккаунтов, установки модификаций (модов) и сборок.
13 |
14 | 1.2. Лаунчер распространяется по лицензии GNU General Public License версии 3.0 (GPL-3.0), что позволяет пользователям
15 | использовать, изменять и распространять Лаунчер в соответствии с условиями данной лицензии.
16 |
17 | 1.3. Исходный код Лаунчера является открытым и доступен для ознакомления, использования и модификации. Пользователь
18 | может получить доступ к исходному коду на официальном репозитории Разработчика.
19 |
20 | ### 2. Сбор и использование данных
21 | 2.1. При каждом запуске Лаунчера автоматически собираются следующие данные:
22 | - Название операционной системы (ОС), установленной на устройстве Пользователя.
23 | - Название и модель процессора, установленного в устройстве Пользователя.
24 | - Количество установленной оперативной памяти на устройстве Пользователя.
25 | - Уникальный идентификатор пользователя (ID), генерируемый для идентификации Пользователя.
26 | - Версия Лаунчера, используемая Пользователем.
27 | - Версии Java, установленные Лаунчером.
28 | - Количество созданных Пользователем сборок в Лаунчере.
29 | - Количество версий Minecraft, установленных через Лаунчер.
30 | - Версии программного обеспечения, используемые в Лаунчере.
31 |
32 | 2.2. Собранные данные передаются на сервера, подконтрольные Разработчику, и используются для следующих целей:
33 | - Получение обратной связи от Пользователей.
34 | - Улучшение общего пользовательского опыта.
35 | - Исправление возможных ошибок и багов в Лаунчере.
36 |
37 | 2.3. Лаунчер предоставляет возможность сбора более полных данных только с
38 | явного согласия Пользователя. Это осуществляется путем нажатия клавиши F12 в окне Лаунчера. Такие данные
39 | остаются на устройстве Пользователя и не передаются на сервера Разработчика. Пользователь вправе самостоятельно
40 | отправить эти данные Разработчику в случае возникновения технических проблем.
41 |
42 | ### 3. Ограничение ответственности
43 | 3.1. Разработчик не несет ответственности за любые повреждения, потерю данных или другие убытки, возникающие в результате использования Лаунчера, программного обеспечения Java или игры
44 | Minecraft.
45 |
46 | 3.2. Разработчик не несет ответственности за любые изменения или удаления файлов Пользователя, связанные с использованием Лаунчера и игры Minecraft.
47 |
48 | 3.3. Разработчик предоставляет Лаунчер как есть (as is), без каких-либо гарантий, явных или подразумеваемых, включая, но не ограничиваясь,
49 | подразумеваемые гарантии товарной пригодности или соответствия определенной цели.
50 |
51 | ### 4. Лицензия и права
52 | 4.1. Лаунчер распространяется по лицензии GNU General Public License версии 3.0 (GPL-3.0). Пользователь имеет право
53 | использовать, копировать, изменять и распространять Лаунчер в соответствии с условиями данной
54 | лицензии.
55 |
56 | 4.2. Исходный код Лаунчера доступен для свободного использования и модификации. Пользователь может вносить изменения в исходный код при условии соблюдения условий лицензии
57 | GPL-3.0.
58 |
59 | 4.3. Любые модификации Лаунчера, распространяемые Пользователем, должны также распространяться под лицензией GPL-3.0.
60 |
61 | ### 5. Прочие условия
62 | 5.1. Разработчик оставляет за собой право вносить изменения в настоящее Соглашение в одностороннем порядке. Изменения вступают в силу с момента публикации новой версии Соглашения на официальном сайте Разработчика.
63 |
64 | 5.2. В случае несогласия Пользователя с изменениями, он обязан прекратить использование Лаунчера. Продолжение использования Лаунчера после внесения изменений считается согласием Пользователя с новыми условиями Соглашения.
65 |
66 | ### 6. Заключительные положения
67 | 6.1. В случае признания какого-либо положения настоящего Соглашения
68 | недействительным или неисполнимым, остальные положения остаются в силе и продолжают действовать в полной
69 | мере.
70 |
71 | 6.2. Настоящее Соглашение является полным и исчерпывающим соглашением между Пользователем и
72 | Разработчиком относительно использования Лаунчера и заменяет собой все предыдущие соглашения, договоренности и
73 | переговоры, как в устной, так и в письменной форме.
74 |
75 | Скачивая с официального сайта и используя продукт FrogLauncher вы подтверждаете, что ознакомились с условиями данного Соглашения и согласны с ними.
--------------------------------------------------------------------------------
/web/js/FrogDownloader.js:
--------------------------------------------------------------------------------
1 | let downloadsProcesses = {};
2 |
3 | class FrogDownloader {
4 | // Подготовить UI к отображению загрузки файла
5 | static prepareUIToDownload(displayName) {
6 | return new Promise(resolve => {
7 | FrogFlyout.setProgress(0);
8 | FrogFlyout.setText(`${MESSAGES.commons.downloading} ${displayName}`, MESSAGES.commons.plsWait);
9 | FrogFlyout.changeMode("progress").then(resolve);
10 | })
11 | }
12 |
13 | // Скачивается ли что-то в данный момент
14 | static isAnythingDownloading = () => {
15 | return Object.keys(downloadsProcesses).length;
16 | }
17 |
18 | // Обновить UI загрузки
19 | static updateDownloadUI = (displayName, progress, current, total) => {
20 | let currentHuman = FrogUtils.humanFileSize(current);
21 | let totalHuman = FrogUtils.humanFileSize(total);
22 |
23 | FrogFlyout.setProgress(progress);
24 | FrogFlyout.setText(`${MESSAGES.commons.downloading} ${displayName}`, `${currentHuman} / ${totalHuman} (${progress}%)`);
25 | return true;
26 | }
27 |
28 | // Обновить UI загрузки (использовать текст)
29 | static updateDownloadUIText = (displayName, progress, current, total) => {
30 | FrogFlyout.setProgress(progress);
31 | FrogFlyout.setText(`${MESSAGES.commons.downloading} ${displayName}`, `${current} / ${total} (${progress}%)`);
32 | return true;
33 | }
34 |
35 | // Скачать файл
36 | static downloadFile = (url, filePath, displayName = null, updateProgress = true) => {
37 | FrogCollector.writeLog(`New download [url=${url}] [displayName=${displayName}]`);
38 | if (displayName === null) {
39 | displayName = path.basename(filePath);
40 | }
41 | let dirname = path.dirname(filePath);
42 | if (!fs.existsSync(dirname)) {
43 | fs.mkdirSync(dirname, {
44 | recursive: true,
45 | });
46 | }
47 |
48 | let received_bytes = 0;
49 | let total_bytes = 0;
50 | let percent = "";
51 |
52 | return new Promise((resolve, reject) => {
53 | FrogDownloader.prepareUIToDownload(displayName).then(() => {
54 | FrogCollector.writeLog(`UI prepared for download`);
55 | request
56 | .get(url)
57 | .on("error", function (error) {
58 | reject(error);
59 | })
60 | .on("response", function (data) {
61 | total_bytes = parseInt(data.headers["content-length"]);
62 | data.pipe(fs.createWriteStream(filePath));
63 | })
64 | .on("data", function (chunk) {
65 | received_bytes += chunk.length;
66 | if (updateProgress) {
67 | percent = Math.round((received_bytes * 100) / total_bytes);
68 | FrogDownloader.updateDownloadUI(displayName, percent, received_bytes, total_bytes);
69 | }
70 | })
71 | .on("end", function () {
72 | FrogCollector.writeLog(`Download completed [displayname=${displayName}]`);
73 | FrogFlyout.setProgress(0);
74 | resolve(true);
75 | });
76 | })
77 | });
78 | }
79 |
80 | // Скачать файл в фоновом режиме
81 | static quietDownloadFile = (url, filePath) => {
82 | FrogCollector.writeLog(`New quiet download [url=${url}]`);
83 | let dirname = path.dirname(filePath);
84 | if (!fs.existsSync(dirname)) {
85 | fs.mkdirSync(dirname, {
86 | recursive: true,
87 | });
88 | }
89 |
90 | let received_bytes = 0;
91 | let total_bytes = 0;
92 |
93 | return new Promise((resolve, reject) => {
94 | request
95 | .get(url)
96 | .on("error", function (error) {
97 | reject(error);
98 | })
99 | .on("response", function (data) {
100 | total_bytes = parseInt(data.headers["content-length"]);
101 | data.pipe(fs.createWriteStream(filePath));
102 | })
103 | .on("data", function (chunk) {
104 | received_bytes += chunk.length;
105 | })
106 | .on("end", function () {
107 | FrogCollector.writeLog(`Quiet download completed`);
108 | resolve(true);
109 | });
110 | })
111 | }
112 | }
--------------------------------------------------------------------------------
/web/js/FrogFlyout.js:
--------------------------------------------------------------------------------
1 | const TOOLTIP_PLACEHOLDER = ``;
2 | let tooltipShowed = false;
3 |
4 | class FrogFlyout {
5 | // Сменить режим панели
6 | static changeMode = (mode) => {
7 | return new Promise((resolve) => {
8 | if (FrogFlyout.getCurrentMode() === mode) {
9 | return resolve(false);
10 | }
11 | if (FrogModals.isModalShown("accounts")) {
12 | FrogModals.hideModal("accounts");
13 | }
14 | if (FrogModals.isModalShown("versions")) {
15 | FrogModals.hideModal("versions");
16 | }
17 | FrogCollector.writeLog(`Flyout: New mode - "${mode}"`);
18 | let allowedModes = ["idle", "progress", "spinner"];
19 | if (!allowedModes.includes(mode)) {
20 | return resolve(false);
21 | }
22 |
23 | let visibleFlyoutElems = $(".flyout .flyout-mode:not(.hidden)");
24 | visibleFlyoutElems.each(function (i, elem) {
25 | animateCSSNode($(elem)[0], "fadeOut").then(() => {
26 | $(`#${$(elem).attr("id")}`).addClass("hidden");
27 | let newModeElem = $(`.flyout > div[data-mode="${mode}"]`);
28 | newModeElem.removeClass("hidden");
29 | animateCSSNode(newModeElem[0], "fadeIn").then(resolve);
30 | })
31 | })
32 | })
33 | }
34 |
35 | // Получить режим UI в настоящее время
36 | static getCurrentMode = () => {
37 | return $(".flyout .flyout-mode:not(.hidden)").data("mode");
38 | }
39 |
40 | // Перевести UI в режим запуска игры (скрыть/заблокировать ненужные элементы)
41 | static setUIStartMode = (startMode) => {
42 | FrogModals.switchToContent();
43 | // Скрываем все кнопки Play в списке версий на главном экране
44 | startMode ? $(".versionsPosters").addClass("start-mode") : $(".versionsPosters").removeClass("start-mode");
45 | FrogCollector.writeLog(`Flyout: UI in start mode: ${startMode}`);
46 | startMode ? $(".flyout").addClass("start-mode") : $(".flyout").removeClass("start-mode");
47 | }
48 |
49 | // Задать текст (задаётся во всех режимах сразу)
50 | static setText = (text = "", description = "") => {
51 | if (text !== "") {
52 | $(".flyout .flyout-mode:not(#versionSelect) .title").text(text);
53 | }
54 | if (description !== "") {
55 | $(".flyout .flyout-mode:not(#versionSelect) .description").show().text(description);
56 | } else {
57 | $(".flyout .flyout-mode:not(#versionSelect) .description").hide();
58 | }
59 | };
60 |
61 | // Задать значение прогресс бара (-1 - бесконечный)
62 | static setProgress = (progress) => {
63 | let progressElem = $(".flyout .progress-pill .inner");
64 | if (progress < -1 || progress > 100) {
65 | return false;
66 | }
67 |
68 | if (progress === -1) {
69 | progressElem.addClass("indeterminate");
70 | return true;
71 | }
72 |
73 | progressElem.removeClass("indeterminate");
74 | progressElem.css("width", `${progress}%`);
75 | return true;
76 | }
77 |
78 | // Показать tooltip
79 | static showTooltip = (menuElement, text) => {
80 | if (tooltipShowed === true) {
81 | return false;
82 | }
83 |
84 | tooltipShowed = true;
85 | let elementPos = $(menuElement)[0].getBoundingClientRect();
86 | $("html").append(TOOLTIP_PLACEHOLDER.replaceAll("$1", text))
87 | let tooltipElement = $(".flymenu-tooltip");
88 | let tooltipPos = $(tooltipElement)[0].getBoundingClientRect();
89 |
90 | let left = elementPos.left + elementPos.width + 18;
91 | let top = elementPos.top + (elementPos.height / 2 - tooltipPos.height / 2);
92 |
93 | $(tooltipElement).css("top", top);
94 | $(tooltipElement).css("left", left);
95 | return true;
96 | }
97 |
98 | // Скрыть tooltip
99 | static hideTooltip = () => {
100 | tooltipShowed = false;
101 | return $(".flymenu-tooltip").remove();
102 | }
103 |
104 | // Заблокировать для взаимодействия FlyMenu
105 | static lockFlymenu = () => {
106 | $(".flymenu").css("pointer-events", "none");
107 | }
108 |
109 | // Разблокировать для взаимодействия FlyMenu
110 | static unlockFlymenu = () => {
111 | $(".flymenu").css("pointer-events", "initial");
112 | }
113 |
114 | // Запустить выбранную версию
115 | static startSelectedVersion = () => {
116 | if (FrogAccountsManager.getActiveAccount() === "none") {
117 | return false;
118 | }
119 | let activeVersion = FrogVersionsManager.getActiveVersion();
120 | FrogStarter.simpleStart(activeVersion);
121 | return true;
122 | }
123 | }
--------------------------------------------------------------------------------
/web/js/FrogElybyManager.js:
--------------------------------------------------------------------------------
1 | const ELYBY_ENDPOINT = "https://authserver.ely.by";
2 | const ELYBY_AUTH_URL = "/auth/authenticate";
3 | const ELYBY_REFRESH_URL = "/auth/refresh";
4 | const ELYBY_VALIDATE_URL = "/auth/validate";
5 |
6 | class FrogElybyManager {
7 | // Войти в аккаунт (возвращает success, data/error)
8 | static loginAccount = async (login, password, totpToken = "") => {
9 | let clientToken = await machineUuid();
10 | if (totpToken !== "") {
11 | password = password + ":" + totpToken;
12 | }
13 | let [isSuccess, response] = await FrogRequests.post(ELYBY_ENDPOINT + ELYBY_AUTH_URL, {
14 | username: login,
15 | password: password,
16 | clientToken: clientToken,
17 | requestUser: true
18 | }, "json", true);
19 | if(isSuccess){
20 | return [true, response, clientToken];
21 | } else {
22 | if (response.responseJSON.errorMessage.match(/Invalid credentials/gim) !== null) {
23 | return [false, "INVALID_CREDENTIALS", clientToken];
24 | } else if (response.responseJSON.errorMessage.match(/two factor auth/gim) !== null) {
25 | return [false, "REQUIRES_TOTP", clientToken];
26 | } else {
27 | return [false, response.responseJSON.errorMessage, clientToken];
28 | }
29 | }
30 | }
31 |
32 | // Обновить access token
33 | static refreshAccessToken = async (authToken) => {
34 | let clientToken = await machineUuid();
35 | let [isSuccess, result] = await FrogRequests.post(ELYBY_ENDPOINT + ELYBY_REFRESH_URL, {
36 | accessToken: authToken,
37 | clientToken: clientToken,
38 | requestUser: true,
39 | }, "json", true);
40 | if(!isSuccess){
41 | return [false, result.toString(), clientToken];
42 | }
43 | return [true, result, clientToken];
44 | }
45 |
46 | // Валидировать access token
47 | static validateAccessToken = async (accessToken) => {
48 | $.post(ELYBY_ENDPOINT + ELYBY_VALIDATE_URL, {
49 | accessToken: accessToken,
50 | }).done(() => {
51 | return true;
52 | }).fail(() => {
53 | return false;
54 | });
55 | }
56 |
57 | // Получить данные аккаунта для авторизации
58 | static getAccountForAuth = async (uuid) => {
59 | let accountData = FrogAccountsManager.getAccount(uuid);
60 | // Проверяем токен
61 | let validationResult = await FrogElybyManager.validateAccessToken(accountData.accessToken);
62 | if (!validationResult) {
63 | // Заново генерируем токен
64 | let [isRefreshSuccess, refreshResult] = await FrogElybyManager.refreshAccessToken(accountData.accessToken);
65 | if (!isRefreshSuccess) {
66 | // Если ошибка генерации - удаляем акк и просим добавить заново
67 | $("#modal-elybyLogin .error").text(MESSAGES.elyby.repeat);
68 | await FrogModals.showModal("elybyLogin");
69 | FrogAccountsManager.deleteAccount(uuid);
70 | FrogFlyout.setUIStartMode(false);
71 | await FrogFlyout.changeMode("idle");
72 | return false;
73 | } else {
74 | // Сохраняем данные после рефреша
75 | let accountsData = FrogAccountsManager.getAccounts();
76 | accountData.accessToken = refreshResult.accessToken;
77 | accountData.nickname = refreshResult.selectedProfile.name;
78 | accountsData[uuid] = accountData;
79 | await FrogAccountsManager.saveAccounts(accountsData);
80 |
81 | // Возвращаем данные
82 | return {
83 | access_token: accountData.accessToken,
84 | client_token: accountData.clientToken,
85 | uuid: accountData.uuid,
86 | name: accountData.nickname,
87 | };
88 | }
89 | } else {
90 | return {
91 | access_token: accountData.accessToken,
92 | client_token: accountData.clientToken,
93 | uuid: accountData.uuid,
94 | name: accountData.nickname,
95 | };
96 | }
97 | }
98 |
99 | // Получить текстуру головы скина Ely.by по нику
100 | static getHeadURLByPlayerNickname = (nickname) => {
101 | return new Promise(resolve => {
102 | let fileUrl = `http://skinsystem.ely.by/skins/${nickname}.png`;
103 | let filePath = path.join(USERDATA_PATH, "elybySkins", `${nickname}.png`);
104 | if (!fs.existsSync(path.dirname(filePath))) {
105 | fs.mkdirSync(path.dirname(filePath));
106 | }
107 | Jimp.read(fileUrl, (err, image) => {
108 | let imgWidth = image.bitmap.width;
109 | let headWidth = imgWidth / 8;
110 | let headResizeSize;
111 | headWidth > 40 ? headResizeSize = headWidth : headResizeSize = 40;
112 | image.crop(headWidth, headWidth, headWidth, headWidth).resize(headResizeSize, headResizeSize, Jimp.RESIZE_NEAREST_NEIGHBOR).writeAsync(filePath).then(() => {
113 | return resolve(filePath);
114 | });
115 | });
116 | });
117 | }
118 | }
--------------------------------------------------------------------------------
/web/js/FrogAccountsUI.js:
--------------------------------------------------------------------------------
1 | class FrogAccountsUI {
2 | // Перезагрузить список доступных аккаунтов
3 | static reloadAccountsManager = () => {
4 | $("#modal-accounts .accounts-list .item").unbind("click");
5 | // Получаем список и активный аккаунт
6 | let accountsList = FrogAccountsManager.getAccounts();
7 | let activeAccount = FrogAccountsManager.getActiveAccount();
8 | $("#modal-accounts .accounts-list .item:not(.placeholder)").remove();
9 |
10 | // Получаем код placeholder`а
11 | let placeholder = $("#modal-accounts .accounts-list .item.placeholder")[0].outerHTML;
12 | placeholder = placeholder.replace(' placeholder', "");
13 | // По placeholder`у добавляем новые элементы
14 | Object.values(accountsList).forEach((acc) => {
15 | let accountType = FrogAccountsUI.typeDisplayName(acc.type);
16 | let imageUrl = `https://minotar.net/avatar/${acc.nickname}/44`;
17 | let editButtonVisible = "none";
18 | if (acc.type === "elyby") {
19 | imageUrl = path.join(USERDATA_PATH, "elybySkins", `${acc.nickname}.png`);
20 | }
21 | if (acc.type === "frog") {
22 | imageUrl = acc.textures.head || imageUrl;
23 | editButtonVisible = "inherit";
24 | }
25 | let preparedPlaceholder = placeholder.replaceAll("$1", acc.nickname).replaceAll("$2", accountType).replaceAll("$3", acc.uuid).replaceAll("$4", imageUrl).replaceAll("$5", acc.type).replaceAll("$6", editButtonVisible);
26 | $("#modal-accounts .accounts-list").append(preparedPlaceholder);
27 | })
28 |
29 | // Помечаем нужные аккаунты в списке активными
30 | $("#modal-accounts .accounts-list .item").each(function () {
31 | if (!$(this).hasClass("placeholder")) {
32 | if ($(this).data("uuid") === activeAccount) {
33 | $(this).addClass("active");
34 | }
35 | if ($(this).data("type") === "microsoft") {
36 | $(this).find(".type.microsoft").show();
37 | }
38 | if ($(this).data("type") === "elyby") {
39 | $(this).find(".type.elyby").show();
40 | }
41 | if ($(this).data("type") === "frog") {
42 | $(this).find(".type.frog").show();
43 | }
44 | $(this).show();
45 | }
46 | })
47 |
48 | // Биндим клик на смену аккаунта
49 | $("#modal-accounts .accounts-list .item, #modal-accounts .accounts-list .item *:not(.delete)").click(function () {
50 | FrogAccountsManager.setActiveAccount($(this).data("uuid"));
51 | FrogModals.hideModal("accounts");
52 | })
53 | return true;
54 | }
55 |
56 | // Перезагрузить кнопку для открытия менеджера аккаунтов
57 | static reloadAccountSelect = () => {
58 | $(".flyout #accountSelectPlaceholder").show();
59 | $(".flyout #accountSelect").hide();
60 | let activeAccount = FrogAccountsManager.getActiveAccount();
61 | let $activeAccount = $("#modal-accounts .accounts-list .item.active");
62 |
63 | if (activeAccount === "none") {
64 | $("#accountSelect .title").text(MESSAGES.commons.notSelected);
65 | $("#accountSelect .icon").hide();
66 | $("#accountSelect .description").hide();
67 | } else {
68 | $("#accountSelect")[0].innerHTML = "";
69 | $("#accountSelect")[0].innerHTML = $activeAccount.html();
70 | $("#accountSelect .check, #accountSelect .buttons").remove();
71 | }
72 | $(".flyout #accountSelectPlaceholder").hide();
73 | $(".flyout #accountSelect").show();
74 | return true;
75 | }
76 |
77 | // Получить текст из типа аккаунта
78 | static typeDisplayName = (type) => {
79 | if (typeof MESSAGES.accounts[type] !== "undefined") {
80 | return MESSAGES.accounts[type];
81 | } else {
82 | return MESSAGES.accounts.unknown;
83 | }
84 | }
85 |
86 | // Показать UI для добавления локального аккаунта
87 | static showAddLocalAccountUI = () => {
88 | $("#modal-accounts .add-local").show();
89 | $("#modal-accounts .add-local").removeClass("hidden");
90 | }
91 |
92 | // Добавить локальный аккаунт из UI
93 | static addLocalAccount = () => {
94 | let txt = $("#modal-accounts .add-local input").val().trim();
95 | FrogAccountsManager.addLocalAccount(txt);
96 | $("#modal-accounts .add-local").addClass("hidden");
97 | $("#modal-accounts .add-local").one("transitionend webkitTransitionEnd oTransitionEnd", () => {
98 | $("#modal-accounts .add-local").hide();
99 | })
100 | $("#modal-accounts .add-local input").val("");
101 | }
102 |
103 | // Добавить Microsoft аккаунт из UI
104 | static addMicrosoftAccount = () => {
105 | FrogModals.hideModal("accounts");
106 | FrogFlyout.setUIStartMode(true);
107 | FrogFlyout.setText(MESSAGES.accounts.addingMs);
108 | FrogFlyout.changeMode("spinner").then(() => {
109 | FrogAccountsManager.addMicrosoftAccount().then(() => {
110 | FrogFlyout.changeMode("idle");
111 | FrogFlyout.setUIStartMode(false);
112 | });
113 | })
114 | }
115 |
116 | // Открыть UI аккаунтов
117 | static open = () => {
118 | if (FrogModals.isModalShown("accounts")) {
119 | return FrogModals.hideModal("accounts");
120 | }
121 | return FrogModals.showModal("accounts");
122 | }
123 | }
--------------------------------------------------------------------------------
/web/js/FrogCollector.js:
--------------------------------------------------------------------------------
1 | class FrogCollector {
2 | // Собрать архив отладки
3 | static collectDebugData = () => {
4 | FrogCollector.writeLog("Collecting debug archive");
5 | machineUuid().then((uniqueId) => {
6 | let archiveName = `FL_debug-${Date.now()}-${uniqueId}`;
7 | let archiveDirPath = path.join(USERDATA_PATH, archiveName);
8 | let filesList = [
9 | path.join(archiveDirPath, "last.log"),
10 | path.join(archiveDirPath, "timestamp"),
11 | path.join(archiveDirPath, "localStorage"),
12 | path.join(archiveDirPath, "data.json")
13 | ];
14 | fs.mkdirSync(archiveDirPath);
15 | fs.writeFileSync(filesList[0], LAST_LOG);
16 | fs.writeFileSync(filesList[1], Date.now().toString());
17 | fs.writeFileSync(filesList[2], JSON.stringify(window.localStorage));
18 | fs.writeFileSync(filesList[3], JSON.stringify({
19 | cpu: os.cpus()[0],
20 | ram: Math.round(os.totalmem() / 1024 / 1024),
21 | arch: os.arch(),
22 | win: os.release(),
23 | hostName: os.hostname(),
24 | homeDir: os.homedir(),
25 | appDataDir: USERDATA_PATH,
26 | resolvedRoot: path.resolve("./"),
27 | uniqueId: uniqueId,
28 | version: LAUNCHER_VERSION,
29 | processVersions: process.versions,
30 | javas: FrogJavaManager.getLocal(),
31 | packsCount: FrogPacks.getPacksList().length,
32 | settings: FrogConfig.readConfig()
33 | }));
34 | const outputPath = path.join(USERDATA_PATH, archiveName + ".zip");
35 | FrogUtils.compressDirectory(outputPath, archiveDirPath).then(() => {
36 | filesList.forEach(file => {
37 | fs.unlinkSync(file);
38 | })
39 | fs.rmdirSync(archiveDirPath);
40 | openExternal(USERDATA_PATH);
41 | })
42 | });
43 | }
44 |
45 | static writeLog = (...data) => {
46 | let logDate = new Date().toISOString();
47 | let newPart = "\n" + "[" + logDate + "] " + data.join(" ")
48 | global.LAST_LOG = global.LAST_LOG + newPart;
49 | ipcRenderer.send("console-log", newPart);
50 | updateConsole();
51 | }
52 |
53 | static collectAndSendStats = async () => {
54 | FrogCollector.writeLog("Collecting stats");
55 | machineUuid().then((uniqueId) => {
56 | let platformInfo = {
57 | name: os.type(),
58 | release: os.release(),
59 | arch: process.arch,
60 | version: os.version(),
61 | };
62 |
63 | let cpuSummary = os.cpus();
64 |
65 | let cpuInfo = {
66 | model: cpuSummary[0].model,
67 | speed: cpuSummary[0].speed,
68 | cores: cpuSummary.length,
69 | };
70 |
71 | let collectedStats = {
72 | platform: platformInfo,
73 | totalRAM: Math.round(os.totalmem() / 1024 / 1024),
74 | cpu: cpuInfo,
75 | uniqueID: uniqueId,
76 | version: LAUNCHER_VERSION,
77 | javas: FrogJavaManager.getLocal(),
78 | packsCount: FrogPacks.getPacksList().length,
79 | versionsInstalledCount: FrogVersionsManager.getInstalledVersionsList().length,
80 | versions: process.versions
81 | };
82 | if (FrogConfig.read("lessDataCollection", false) === true) {
83 | // Меньше собираем данные
84 | collectedStats = {
85 | uniqueID: uniqueId,
86 | version: LAUNCHER_VERSION
87 | }
88 | }
89 | $.get(
90 | STATS_URL + encodeURIComponent(JSON.stringify(collectedStats)),
91 | () => {
92 | FrogCollector.writeLog("Stats sent successfully");
93 | return true;
94 | }
95 | );
96 | });
97 | }
98 |
99 | static setupLogHijacking = () => {
100 | // Добавляем хэндлинг ошибок
101 | window.onerror = (errorMsg, url, lineNumber) => {
102 | FrogCollector.writeLog("========================");
103 | FrogCollector.writeLog("window.onerror triggered at", Date.now());
104 | FrogCollector.writeLog("Message:", errorMsg);
105 | FrogCollector.writeLog("URL:", url);
106 | FrogCollector.writeLog("Line number:", lineNumber);
107 | FrogCollector.writeLog("========================");
108 | };
109 |
110 | window.addEventListener("error", function (e) {
111 | FrogCollector.writeLog("========================");
112 | FrogCollector.writeLog("error event triggered at", Date.now());
113 | FrogCollector.writeLog("Message:", e.error.message);
114 | FrogCollector.writeLog("Full JSON:", JSON.stringify(e));
115 | FrogCollector.writeLog("========================");
116 | });
117 |
118 | // Заменяем функции консоли
119 | let oldConsole = console;
120 | let newConsole = {};
121 | newConsole.log = function (...log) {
122 | oldConsole.log(...log);
123 | FrogCollector.writeLog("BROWSER:", ...log);
124 | };
125 |
126 | newConsole.info = function (...log) {
127 | oldConsole.info(...log);
128 | FrogCollector.writeLog("BROWSER:", ...log);
129 | };
130 |
131 | newConsole.warn = function (...log) {
132 | oldConsole.warn(...log);
133 | FrogCollector.writeLog("BROWSER:", ...log);
134 | };
135 |
136 | newConsole.error = function (...log) {
137 | oldConsole.error(...log);
138 | FrogCollector.writeLog("BROWSER:", ...log);
139 | };
140 | window.console = newConsole;
141 | return true;
142 | }
143 | }
--------------------------------------------------------------------------------