Merge branch 'feat/VersionsExplorer' of https://git.popa-popa.ru/DIKER/popa-launcher into feat/VersionsExplorer
This commit is contained in:
154
package-lock.json
generated
154
package-lock.json
generated
@ -8,7 +8,6 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/notarize": "^3.0.0",
|
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^7.2.0",
|
"@mui/icons-material": "^7.2.0",
|
||||||
@ -33,6 +32,7 @@
|
|||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"skinview3d": "^3.4.1",
|
"skinview3d": "^3.4.1",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.178.0",
|
"three": "^0.178.0",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
@ -2275,19 +2275,6 @@
|
|||||||
"node": ">=12.13.0"
|
"node": ">=12.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/notarize": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-uQQSlOiJnqRkTL1wlEBAxe90nVN/Fc/hEmk0bqpKk8nKjV1if/tXLHKUPePtv9Xsx90PtZU8aidx5lAiOpjkQQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "^4.4.0",
|
|
||||||
"promise-retry": "^2.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 22.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/osx-sign": {
|
"node_modules/@electron/osx-sign": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz",
|
||||||
@ -4469,6 +4456,12 @@
|
|||||||
"@sinonjs/commons": "^3.0.0"
|
"@sinonjs/commons": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@socket.io/component-emitter": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
"node_modules/@svgr/babel-plugin-add-jsx-attribute": {
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz",
|
||||||
@ -10619,6 +10612,66 @@
|
|||||||
"once": "^1.4.0"
|
"once": "^1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/engine.io-client": {
|
||||||
|
"version": "6.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
|
||||||
|
"integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1",
|
||||||
|
"engine.io-parser": "~5.2.1",
|
||||||
|
"ws": "~8.17.1",
|
||||||
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-client/node_modules/ws": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/engine.io-parser": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "0.9.1",
|
"version": "0.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
|
||||||
@ -10682,6 +10735,7 @@
|
|||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
|
||||||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/error-ex": {
|
"node_modules/error-ex": {
|
||||||
@ -20088,6 +20142,7 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
|
||||||
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
"integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"err-code": "^2.0.2",
|
"err-code": "^2.0.2",
|
||||||
@ -20999,6 +21054,7 @@
|
|||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4"
|
"node": ">= 4"
|
||||||
@ -22107,6 +22163,68 @@
|
|||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socket.io-client": {
|
||||||
|
"version": "4.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
|
||||||
|
"integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.2",
|
||||||
|
"engine.io-client": "~6.6.1",
|
||||||
|
"socket.io-parser": "~4.2.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-client/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser": {
|
||||||
|
"version": "4.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||||
|
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
|
"debug": "~4.3.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socket.io-parser/node_modules/debug": {
|
||||||
|
"version": "4.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sockjs": {
|
"node_modules/sockjs": {
|
||||||
"version": "0.3.24",
|
"version": "0.3.24",
|
||||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
|
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz",
|
||||||
@ -25093,6 +25211,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/y18n": {
|
"node_modules/y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
|||||||
@ -105,7 +105,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/notarize": "^3.0.0",
|
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/styled": "^11.14.1",
|
"@emotion/styled": "^11.14.1",
|
||||||
"@mui/icons-material": "^7.2.0",
|
"@mui/icons-material": "^7.2.0",
|
||||||
@ -130,6 +129,7 @@
|
|||||||
"react-router-dom": "^7.3.0",
|
"react-router-dom": "^7.3.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"skinview3d": "^3.4.1",
|
"skinview3d": "^3.4.1",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"stream-browserify": "^3.0.0",
|
"stream-browserify": "^3.0.0",
|
||||||
"three": "^0.178.0",
|
"three": "^0.178.0",
|
||||||
"util": "^0.12.5",
|
"util": "^0.12.5",
|
||||||
@ -206,7 +206,6 @@
|
|||||||
"productName": "popa-launcher",
|
"productName": "popa-launcher",
|
||||||
"appId": "org.erb.ElectronReact",
|
"appId": "org.erb.ElectronReact",
|
||||||
"asar": true,
|
"asar": true,
|
||||||
"afterSign": ".erb/scripts/notarize.js",
|
|
||||||
"asarUnpack": "**\\*.{node,dll}",
|
"asarUnpack": "**\\*.{node,dll}",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@ -263,7 +262,7 @@
|
|||||||
],
|
],
|
||||||
"publish": {
|
"publish": {
|
||||||
"provider": "generic",
|
"provider": "generic",
|
||||||
"url": "https://git.popa-popa.ru/DIKER/popa-launcher/releases/download/v${version}",
|
"url": "https://git.popa-popa.ru/DIKER/popa-launcher/releases/download/latest",
|
||||||
"channel": "latest",
|
"channel": "latest",
|
||||||
"requestHeaders": {
|
"requestHeaders": {
|
||||||
"Authorization": "token ${env.GH_TOKEN}"
|
"Authorization": "token ${env.GH_TOKEN}"
|
||||||
|
|||||||
4
release/app/package-lock.json
generated
4
release/app/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "Popa Launcher",
|
"description": "Popa Launcher",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@ -96,7 +96,16 @@ const ensureTray = () => {
|
|||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Выход',
|
label: 'Выход',
|
||||||
click: () => app.quit(),
|
click: () => {
|
||||||
|
isQuitting = true;
|
||||||
|
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.removeAllListeners('close');
|
||||||
|
mainWindow.destroy(); // ⬅ КЛЮЧЕВО
|
||||||
|
}
|
||||||
|
|
||||||
|
app.quit();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -119,6 +128,7 @@ const applyLoginItemSettings = () => {
|
|||||||
|
|
||||||
let tray: Tray | null = null;
|
let tray: Tray | null = null;
|
||||||
let isAuthed = false;
|
let isAuthed = false;
|
||||||
|
let isQuitting = false;
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null;
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
@ -152,7 +162,19 @@ function buildTrayMenu() {
|
|||||||
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Показать', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
|
{ label: 'Показать', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
|
||||||
{ label: 'Выход', click: () => app.quit() },
|
{
|
||||||
|
label: 'Выход',
|
||||||
|
click: () => {
|
||||||
|
isQuitting = true;
|
||||||
|
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.removeAllListeners('close');
|
||||||
|
mainWindow.destroy(); // ⬅ КЛЮЧЕВО
|
||||||
|
}
|
||||||
|
|
||||||
|
app.quit();
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
tray?.setContextMenu(Menu.buildFromTemplate(template));
|
tray?.setContextMenu(Menu.buildFromTemplate(template));
|
||||||
@ -259,9 +281,8 @@ const createWindow = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.on('close', (e) => {
|
mainWindow.on('close', (e) => {
|
||||||
if (launcherSettings.closeToTray) {
|
if (!isQuitting && launcherSettings.closeToTray) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ensureTray();
|
|
||||||
mainWindow?.hide();
|
mainWindow?.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -317,6 +338,10 @@ app.on('window-all-closed', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.on('before-quit', () => {
|
||||||
|
isQuitting = true;
|
||||||
|
});
|
||||||
|
|
||||||
app
|
app
|
||||||
.whenReady()
|
.whenReady()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ interface HeadAvatarProps {
|
|||||||
skinUrl?: string;
|
skinUrl?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
version?: number; // ✅ добавили
|
version?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SKIN =
|
const DEFAULT_SKIN =
|
||||||
@ -14,15 +14,17 @@ export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
|||||||
skinUrl,
|
skinUrl,
|
||||||
size = 24,
|
size = 24,
|
||||||
style,
|
style,
|
||||||
version = 0, // ✅ дефолт
|
version = 0,
|
||||||
...canvasProps
|
...canvasProps
|
||||||
}) => {
|
}) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const requestIdRef = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const baseUrl = skinUrl?.trim() ? skinUrl : DEFAULT_SKIN;
|
requestIdRef.current += 1;
|
||||||
|
const requestId = requestIdRef.current;
|
||||||
|
|
||||||
// ✅ cache-bust: чтобы браузер НЕ отдавал старую картинку
|
const baseUrl = skinUrl?.trim() ? skinUrl : DEFAULT_SKIN;
|
||||||
const finalSkinUrl = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}v=${version}`;
|
const finalSkinUrl = `${baseUrl}${baseUrl.includes('?') ? '&' : '?'}v=${version}`;
|
||||||
|
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
@ -33,6 +35,9 @@ export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
|||||||
img.src = finalSkinUrl;
|
img.src = finalSkinUrl;
|
||||||
|
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
|
// ✅ игнорим старые onload
|
||||||
|
if (requestIdRef.current !== requestId) return;
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
@ -47,9 +52,16 @@ export const HeadAvatar: React.FC<HeadAvatarProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = (e) => {
|
img.onerror = (e) => {
|
||||||
|
if (requestIdRef.current !== requestId) return;
|
||||||
console.error('Не удалось загрузить скин для HeadAvatar:', e);
|
console.error('Не удалось загрузить скин для HeadAvatar:', e);
|
||||||
};
|
};
|
||||||
}, [skinUrl, size, version]); // ✅ version добавили
|
|
||||||
|
return () => {
|
||||||
|
// ✅ гарантированно “убиваем” обработчики старого запроса
|
||||||
|
img.onload = null;
|
||||||
|
img.onerror = null;
|
||||||
|
};
|
||||||
|
}, [skinUrl, size, version]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<canvas
|
<canvas
|
||||||
|
|||||||
@ -49,6 +49,7 @@ import GradientTextField from '../components/GradientTextField';
|
|||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import type { SxProps, Theme } from '@mui/material/styles';
|
import type { SxProps, Theme } from '@mui/material/styles';
|
||||||
|
import { getWsBaseUrl } from '../realtime/wsBase';
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -80,6 +81,18 @@ const GLASS_BG =
|
|||||||
const GLASS_BORDER = '1px solid rgba(255,255,255,0.08)';
|
const GLASS_BORDER = '1px solid rgba(255,255,255,0.08)';
|
||||||
const GLASS_SHADOW = '0 1.2vw 3.2vw rgba(0,0,0,0.55)';
|
const GLASS_SHADOW = '0 1.2vw 3.2vw rgba(0,0,0,0.55)';
|
||||||
|
|
||||||
|
function upsertIntoList<T extends { id: string }>(list: T[], item: T) {
|
||||||
|
const idx = list.findIndex((x) => x.id === item.id);
|
||||||
|
if (idx === -1) return [item, ...list];
|
||||||
|
const copy = list.slice();
|
||||||
|
copy[idx] = item;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFromList<T extends { id: string }>(list: T[], id: string) {
|
||||||
|
return list.filter((x) => x.id !== id);
|
||||||
|
}
|
||||||
|
|
||||||
export default function Marketplace() {
|
export default function Marketplace() {
|
||||||
const [marketLoading, setMarketLoading] = useState<boolean>(false);
|
const [marketLoading, setMarketLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -154,6 +167,91 @@ export default function Marketplace() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type MarketListedPayload = { serverIp: string; item: MarketplaceItemResponse };
|
||||||
|
type MarketSoldPayload = { serverIp: string; itemId: string };
|
||||||
|
type MarketPricePayload = { serverIp: string; item: MarketplaceItemResponse };
|
||||||
|
type MarketCancelledPayload = { serverIp: string; itemId: string };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedServer) return;
|
||||||
|
|
||||||
|
const serverIp = selectedServer.ip;
|
||||||
|
|
||||||
|
const base = 'wss://minecraft.api.popa-popa.ru'
|
||||||
|
const wsUrl = `${base}/ws/marketplace?server_ip=${encodeURIComponent(serverIp)}`;
|
||||||
|
|
||||||
|
const ws = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
const ping = window.setInterval(() => {
|
||||||
|
if (ws.readyState === WebSocket.OPEN) ws.send('ping');
|
||||||
|
}, 25000);
|
||||||
|
|
||||||
|
ws.onmessage = (ev) => {
|
||||||
|
let msg: any;
|
||||||
|
try {
|
||||||
|
msg = JSON.parse(ev.data);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.server_ip !== serverIp && msg.serverIp !== serverIp) return;
|
||||||
|
|
||||||
|
switch (msg.event) {
|
||||||
|
case 'market:item_listed': {
|
||||||
|
const item: MarketplaceItemResponse = msg.item;
|
||||||
|
setMarketItems((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
if (prev.page !== 1) return prev;
|
||||||
|
return { ...prev, items: upsertIntoList(prev.items, item) };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (item?.seller_name === username) {
|
||||||
|
setMyItems((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
if (prev.page !== 1) return prev;
|
||||||
|
return { ...prev, items: upsertIntoList(prev.items, item) };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'market:item_sold': {
|
||||||
|
const itemId: string = msg.item_id ?? msg.itemId;
|
||||||
|
setMarketItems((prev) => (prev ? { ...prev, items: removeFromList(prev.items, itemId) } : prev));
|
||||||
|
setMyItems((prev) => (prev ? { ...prev, items: removeFromList(prev.items, itemId) } : prev));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'market:item_cancelled': {
|
||||||
|
const itemId: string = msg.item_id ?? msg.itemId;
|
||||||
|
setMarketItems((prev) => (prev ? { ...prev, items: removeFromList(prev.items, itemId) } : prev));
|
||||||
|
setMyItems((prev) => (prev ? { ...prev, items: removeFromList(prev.items, itemId) } : prev));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'market:item_price_updated': {
|
||||||
|
const item: MarketplaceItemResponse = msg.item;
|
||||||
|
setMarketItems((prev) => (prev ? { ...prev, items: upsertIntoList(prev.items, item) } : prev));
|
||||||
|
setMyItems((prev) => (prev ? { ...prev, items: upsertIntoList(prev.items, item) } : prev));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = (e) => {
|
||||||
|
console.error('Marketplace WS error:', e);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = () => {
|
||||||
|
window.clearInterval(ping);
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.clearInterval(ping);
|
||||||
|
ws.close();
|
||||||
|
};
|
||||||
|
}, [selectedServer?.ip, username]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tabValue !== 2) return;
|
if (tabValue !== 2) return;
|
||||||
if (!selectedServer) return;
|
if (!selectedServer) return;
|
||||||
@ -285,7 +383,7 @@ export default function Marketplace() {
|
|||||||
const loadMarketItems = async (serverIp: string, pageNumber: number) => {
|
const loadMarketItems = async (serverIp: string, pageNumber: number) => {
|
||||||
try {
|
try {
|
||||||
setMarketLoading(true);
|
setMarketLoading(true);
|
||||||
const marketData = await fetchMarketplace(serverIp, pageNumber, 10);
|
const marketData = await fetchMarketplace(serverIp, pageNumber, 12);
|
||||||
setMarketItems(marketData);
|
setMarketItems(marketData);
|
||||||
setPage(marketData.page);
|
setPage(marketData.page);
|
||||||
setTotalPages(marketData.pages);
|
setTotalPages(marketData.pages);
|
||||||
@ -988,6 +1086,7 @@ export default function Marketplace() {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
ml: '25%',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
background: 'rgba(20,20,20,0.78)',
|
background: 'rgba(20,20,20,0.78)',
|
||||||
borderRadius: '2.0vw',
|
borderRadius: '2.0vw',
|
||||||
|
|||||||
14
src/renderer/realtime/wsBase.ts
Normal file
14
src/renderer/realtime/wsBase.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
export function getWsBaseUrl(): string {
|
||||||
|
// 1) если ты пробрасываешь конфиг в window
|
||||||
|
const w = window as any;
|
||||||
|
if (w.__ENV__?.WS_BASE) return String(w.__ENV__.WS_BASE);
|
||||||
|
|
||||||
|
// 2) если открыто с https/http — строим ws/wss автоматически
|
||||||
|
if (typeof window !== 'undefined' && window.location?.origin) {
|
||||||
|
const origin = window.location.origin; // http(s)://host
|
||||||
|
return origin.replace(/^http/, 'ws');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) дефолт
|
||||||
|
return 'wss://minecraft.api.popa-popa.ru';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user