mirror of
https://github.com/amiayweb/Hytale-F2P.git
synced 2026-02-26 04:41:46 -03:00
Merge branch 'develop' of https://github.com/amiayweb/Hytale-F2P into develop
This commit is contained in:
@@ -635,20 +635,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Installation effects overlay -->
|
||||
<div id="installationEffects" class="installation-effects" style="display: none;">
|
||||
<div class="space-effects">
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
<div class="warp-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="chatUsernameModal" class="chat-username-modal" style="display: none;">
|
||||
<div class="chat-username-modal-content">
|
||||
<div class="chat-username-modal-header">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
let CF_API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
|
||||
let API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
|
||||
const CURSEFORGE_API = 'https://api.curseforge.com/v1';
|
||||
const HYTALE_GAME_ID = 70216;
|
||||
|
||||
@@ -12,7 +12,9 @@ let modsTotalPages = 1;
|
||||
|
||||
export async function initModsManager() {
|
||||
try {
|
||||
console.log('Loaded API Key:', CF_API_KEY ? 'Yes' : 'No');
|
||||
if (window.electronAPI && window.electronAPI.getEnvVar) {
|
||||
console.log('Loaded API Key:', API_KEY ? 'Yes' : 'No');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load API Key:', err);
|
||||
}
|
||||
@@ -194,7 +196,7 @@ async function loadBrowseMods() {
|
||||
browseContainer.innerHTML = `<div class="loading-mods"><div class="loading-spinner"></div><span>${window.i18n ? window.i18n.t('mods.loadingMods') : 'Loading mods from CurseForge...'}</span></div>`;
|
||||
|
||||
try {
|
||||
if (!CF_API_KEY || CF_API_KEY.length < 10) {
|
||||
if (!API_KEY || API_KEY.length < 10) {
|
||||
browseContainer.innerHTML = `
|
||||
<div class=\"empty-browse-mods\">
|
||||
<i class=\"fas fa-key\"></i>
|
||||
@@ -221,7 +223,7 @@ async function loadBrowseMods() {
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'x-api-key': CF_API_KEY,
|
||||
'x-api-key': API_KEY,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
@@ -207,11 +207,6 @@ function setupSettingsElements() {
|
||||
}
|
||||
|
||||
|
||||
if (closeLauncherCheck) {
|
||||
closeLauncherCheck.addEventListener('change', saveCloseLauncher);
|
||||
}
|
||||
|
||||
|
||||
// UUID event listeners
|
||||
if (copyUuidBtn) {
|
||||
copyUuidBtn.addEventListener('click', copyCurrentUuid);
|
||||
@@ -397,31 +392,6 @@ async function loadCloseLauncher() {
|
||||
}
|
||||
|
||||
|
||||
async function saveCloseLauncher() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveCloseLauncher && closeLauncherCheck) {
|
||||
const enabled = closeLauncherCheck.checked;
|
||||
await window.electronAPI.saveCloseLauncher(enabled);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving close launcher setting:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadCloseLauncher() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.loadCloseLauncher) {
|
||||
const enabled = await window.electronAPI.loadCloseLauncher();
|
||||
if (closeLauncherCheck) {
|
||||
closeLauncherCheck.checked = enabled;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading close launcher setting:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function savePlayerName() {
|
||||
try {
|
||||
if (!window.electronAPI || !settingsPlayerName) return;
|
||||
|
||||
@@ -638,9 +638,6 @@ function setupUI() {
|
||||
// Setup retry button
|
||||
setupRetryButton();
|
||||
|
||||
// Setup draggable progress bar
|
||||
setupProgressDrag();
|
||||
|
||||
lockPlayButton(true);
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -741,6 +738,7 @@ window.LauncherUI = {
|
||||
|
||||
// Make installation effects globally available
|
||||
|
||||
|
||||
// Draggable progress bar functionality
|
||||
function setupProgressDrag() {
|
||||
if (!progressOverlay) return;
|
||||
|
||||
818
GUI/style.css
818
GUI/style.css
@@ -1770,252 +1770,252 @@ body {
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress Error and Retry Styles */
|
||||
.progress-error-container {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(239, 68, 68, 0.3);
|
||||
animation: errorSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes errorSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-error-message {
|
||||
color: #f87171;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.progress-retry-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.progress-retry-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.progress-retry-info {
|
||||
color: #fbbf24;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-retry-btn {
|
||||
background: linear-gradient(135deg, #dc2626, #ef4444);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.progress-retry-btn:hover {
|
||||
background: linear-gradient(135deg, #b91c1c, #dc2626);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
||||
}
|
||||
|
||||
.progress-retry-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 6px rgba(220, 38, 38, 0.3);
|
||||
}
|
||||
|
||||
.progress-retry-btn:disabled {
|
||||
background: linear-gradient(135deg, #4b5563, #6b7280);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Progress overlay error state */
|
||||
.progress-overlay.error-state {
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.5),
|
||||
0 0 30px rgba(239, 68, 68, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.progress-overlay.error-state #progressBarFill {
|
||||
background: linear-gradient(90deg, #dc2626, #ef4444);
|
||||
animation: errorPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes errorPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Error type specific styling */
|
||||
.progress-error-container.error-network {
|
||||
border-top-color: rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-network .progress-error-message {
|
||||
color: #60a5fa;
|
||||
text-shadow: 0 0 8px rgba(96, 165, 250, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-stall {
|
||||
border-top-color: rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-stall .progress-error-message {
|
||||
color: #fbbf24;
|
||||
text-shadow: 0 0 8px rgba(251, 191, 36, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-file {
|
||||
border-top-color: rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-file .progress-error-message {
|
||||
color: #f87171;
|
||||
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-permission {
|
||||
border-top-color: rgba(168, 85, 247, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-permission .progress-error-message {
|
||||
color: #a855f7;
|
||||
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-server {
|
||||
border-top-color: rgba(236, 72, 153, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-server .progress-error-message {
|
||||
color: #ec4899;
|
||||
text-shadow: 0 0 8px rgba(236, 72, 153, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-corruption {
|
||||
border-top-color: rgba(220, 38, 38, 0.8);
|
||||
}
|
||||
|
||||
.progress-error-container.error-corruption .progress-error-message {
|
||||
color: #dc2626;
|
||||
text-shadow: 0 0 8px rgba(220, 38, 38, 0.6);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-error-container.error-butler {
|
||||
border-top-color: rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-butler .progress-error-message {
|
||||
color: #f59e0b;
|
||||
text-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-space {
|
||||
border-top-color: rgba(168, 85, 247, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-space .progress-error-message {
|
||||
color: #a855f7;
|
||||
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-conflict {
|
||||
border-top-color: rgba(6, 182, 212, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-conflict .progress-error-message {
|
||||
color: #06b6d4;
|
||||
text-shadow: 0 0 8px rgba(6, 182, 212, 0.4);
|
||||
}
|
||||
|
||||
/* Connection quality indicators */
|
||||
.progress-details {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-details #progressSize {
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
|
||||
/* Enhanced retry button states */
|
||||
.progress-retry-btn.retrying {
|
||||
background: linear-gradient(135deg, #059669, #10b981);
|
||||
animation: retryingPulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes retryingPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* Network status indicator (optional future enhancement) */
|
||||
.network-status {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
|
||||
}
|
||||
|
||||
.network-status.poor {
|
||||
background: #f87171;
|
||||
box-shadow: 0 0 6px rgba(248, 113, 113, 0.6);
|
||||
}
|
||||
|
||||
.network-status.fair {
|
||||
background: #fbbf24;
|
||||
box-shadow: 0 0 6px rgba(251, 191, 36, 0.6);
|
||||
}
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress Error and Retry Styles */
|
||||
.progress-error-container {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid rgba(239, 68, 68, 0.3);
|
||||
animation: errorSlideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes errorSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.progress-error-message {
|
||||
color: #f87171;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.progress-retry-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.progress-retry-buttons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.progress-retry-info {
|
||||
color: #fbbf24;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.7rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.progress-retry-btn {
|
||||
background: linear-gradient(135deg, #dc2626, #ef4444);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.3);
|
||||
white-space: nowrap;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.progress-retry-btn:hover {
|
||||
background: linear-gradient(135deg, #b91c1c, #dc2626);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.4);
|
||||
}
|
||||
|
||||
.progress-retry-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 6px rgba(220, 38, 38, 0.3);
|
||||
}
|
||||
|
||||
.progress-retry-btn:disabled {
|
||||
background: linear-gradient(135deg, #4b5563, #6b7280);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Progress overlay error state */
|
||||
.progress-overlay.error-state {
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(0, 0, 0, 0.5),
|
||||
0 0 30px rgba(239, 68, 68, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.progress-overlay.error-state #progressBarFill {
|
||||
background: linear-gradient(90deg, #dc2626, #ef4444);
|
||||
animation: errorPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes errorPulse {
|
||||
0%, 100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Error type specific styling */
|
||||
.progress-error-container.error-network {
|
||||
border-top-color: rgba(59, 130, 246, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-network .progress-error-message {
|
||||
color: #60a5fa;
|
||||
text-shadow: 0 0 8px rgba(96, 165, 250, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-stall {
|
||||
border-top-color: rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-stall .progress-error-message {
|
||||
color: #fbbf24;
|
||||
text-shadow: 0 0 8px rgba(251, 191, 36, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-file {
|
||||
border-top-color: rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-file .progress-error-message {
|
||||
color: #f87171;
|
||||
text-shadow: 0 0 8px rgba(248, 113, 113, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-permission {
|
||||
border-top-color: rgba(168, 85, 247, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-permission .progress-error-message {
|
||||
color: #a855f7;
|
||||
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-server {
|
||||
border-top-color: rgba(236, 72, 153, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-server .progress-error-message {
|
||||
color: #ec4899;
|
||||
text-shadow: 0 0 8px rgba(236, 72, 153, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-corruption {
|
||||
border-top-color: rgba(220, 38, 38, 0.8);
|
||||
}
|
||||
|
||||
.progress-error-container.error-corruption .progress-error-message {
|
||||
color: #dc2626;
|
||||
text-shadow: 0 0 8px rgba(220, 38, 38, 0.6);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.progress-error-container.error-butler {
|
||||
border-top-color: rgba(245, 158, 11, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-butler .progress-error-message {
|
||||
color: #f59e0b;
|
||||
text-shadow: 0 0 8px rgba(245, 158, 11, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-space {
|
||||
border-top-color: rgba(168, 85, 247, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-space .progress-error-message {
|
||||
color: #a855f7;
|
||||
text-shadow: 0 0 8px rgba(168, 85, 247, 0.4);
|
||||
}
|
||||
|
||||
.progress-error-container.error-conflict {
|
||||
border-top-color: rgba(6, 182, 212, 0.5);
|
||||
}
|
||||
|
||||
.progress-error-container.error-conflict .progress-error-message {
|
||||
color: #06b6d4;
|
||||
text-shadow: 0 0 8px rgba(6, 182, 212, 0.4);
|
||||
}
|
||||
|
||||
/* Connection quality indicators */
|
||||
.progress-details {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-details #progressSize {
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
|
||||
/* Enhanced retry button states */
|
||||
.progress-retry-btn.retrying {
|
||||
background: linear-gradient(135deg, #059669, #10b981);
|
||||
animation: retryingPulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes retryingPulse {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 2px 8px rgba(5, 150, 105, 0.3);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(5, 150, 105, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* Network status indicator (optional future enhancement) */
|
||||
.network-status {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
box-shadow: 0 0 6px rgba(16, 185, 129, 0.6);
|
||||
}
|
||||
|
||||
.network-status.poor {
|
||||
background: #f87171;
|
||||
box-shadow: 0 0 6px rgba(248, 113, 113, 0.6);
|
||||
}
|
||||
|
||||
.network-status.fair {
|
||||
background: #fbbf24;
|
||||
box-shadow: 0 0 6px rgba(251, 191, 36, 0.6);
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
@@ -5990,167 +5990,167 @@ select.settings-input option {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
/* Launcher Update Modal Styles */
|
||||
.update-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100000;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.update-modal {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
border: 2px solid rgba(147, 51, 234, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(147, 51, 234, 0.3);
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.update-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.update-header i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.update-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.update-content {
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.update-version {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #9333ea;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.current-version {
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.release-notes {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-left: 3px solid #9333ea;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.update-progress {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #9333ea 0%, #7c3aed 100%);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
color: #aaa;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.update-note {
|
||||
background: rgba(147, 51, 234, 0.1);
|
||||
border: 1px solid rgba(147, 51, 234, 0.3);
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.update-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.update-actions button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.update-actions .btn-primary {
|
||||
background: linear-gradient(135deg, #9333ea 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.update-actions .btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(147, 51, 234, 0.4);
|
||||
}
|
||||
|
||||
.update-actions .btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e0e0e0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.update-actions .btn-secondary:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.update-actions button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/* Launcher Update Modal Styles */
|
||||
.update-modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
backdrop-filter: blur(10px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100000;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
.update-modal {
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
border: 2px solid rgba(147, 51, 234, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
box-shadow: 0 20px 60px rgba(147, 51, 234, 0.3);
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(30px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.update-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.update-header i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.update-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.update-content {
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.update-version {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #9333ea;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.current-version {
|
||||
color: #888;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.release-notes {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-left: 3px solid #9333ea;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.update-progress {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #9333ea 0%, #7c3aed 100%);
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
color: #aaa;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.update-note {
|
||||
background: rgba(147, 51, 234, 0.1);
|
||||
border: 1px solid rgba(147, 51, 234, 0.3);
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.update-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.update-actions button {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.update-actions .btn-primary {
|
||||
background: linear-gradient(135deg, #9333ea 0%, #7c3aed 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.update-actions .btn-primary:hover:not(:disabled) {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(147, 51, 234, 0.4);
|
||||
}
|
||||
|
||||
.update-actions .btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #e0e0e0;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.update-actions .btn-secondary:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.update-actions button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
2
PKGBUILD
2
PKGBUILD
@@ -29,5 +29,5 @@ package() {
|
||||
mkdir -p "$pkgdir/opt/$_pkgname"
|
||||
cp -r "$_pkgname/dist/linux-unpacked/"* "$pkgdir/opt/$_pkgname"
|
||||
install -Dm644 "$_pkgname.desktop" "$pkgdir/usr/share/applications/$_pkgname.desktop"
|
||||
install -Dm644 "$_pkgname/icon.png" "$pkgdir/usr/share/icons/hicolor/512x512/apps/$_pkgname.png"
|
||||
install -Dm644 "$_pkgname/GUI/icon.png" "$pkgdir/usr/share/icons/hicolor/256x256/apps/$_pkgname.png"
|
||||
}
|
||||
|
||||
@@ -15,12 +15,6 @@ class AppUpdater {
|
||||
}
|
||||
|
||||
setupAutoUpdater() {
|
||||
// Enable dev mode for testing (reads dev-app-update.yml)
|
||||
// Only enable in development, not in production builds
|
||||
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
||||
autoUpdater.forceDevUpdateConfig = true;
|
||||
console.log('Dev update mode enabled - using dev-app-update.yml');
|
||||
}
|
||||
|
||||
// Configure logger for electron-updater
|
||||
// Create a compatible logger interface
|
||||
|
||||
@@ -406,5 +406,6 @@ module.exports = {
|
||||
getJavaDetection,
|
||||
downloadJRE,
|
||||
extractJRE,
|
||||
retryJREDownload,
|
||||
JAVA_EXECUTABLE
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ const { getModsPath, getProfilesDir } = require('../core/paths');
|
||||
const { saveModsToConfig, loadModsFromConfig } = require('../core/config');
|
||||
const profileManager = require('./profileManager');
|
||||
|
||||
const CF_API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
|
||||
const API_KEY = "$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32";
|
||||
|
||||
/**
|
||||
* Get the physical mods path for a specific profile.
|
||||
@@ -120,7 +120,7 @@ async function downloadMod(modInfo) {
|
||||
if (!downloadUrl && modInfo.fileId && modInfo.modId) {
|
||||
const response = await axios.get(`https://api.curseforge.com/v1/mods/${modInfo.modId || modInfo.curseForgeId}/files/${modInfo.fileId || modInfo.curseForgeFileId}`, {
|
||||
headers: {
|
||||
'x-api-key': modInfo.apiKey || CF_API_KEY,
|
||||
'x-api-key': modInfo.apiKey || API_KEY,
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
@@ -393,7 +393,7 @@ async function syncModsForCurrentProfile() {
|
||||
...mod,
|
||||
modId: mod.curseForgeId,
|
||||
fileId: mod.curseForgeFileId || mod.fileId,
|
||||
apiKey: CF_API_KEY
|
||||
apiKey: API_KEY
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`[ModManager] Auto-repair failed for "${mod.name}": ${err.message}`);
|
||||
|
||||
@@ -103,40 +103,36 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Referer': 'https://launcher.hytale.com/',
|
||||
'Connection': 'keep-alive'
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||||
'Accept': '*/*'
|
||||
};
|
||||
|
||||
// Add Range header for resume capability
|
||||
// Add Range header ONLY if resuming (startByte > 0)
|
||||
if (startByte > 0) {
|
||||
headers['Range'] = `bytes=${startByte}-`;
|
||||
console.log(`Adding Range header: bytes=${startByte}-`);
|
||||
} else {
|
||||
console.log('Fresh download, no Range header');
|
||||
}
|
||||
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: url,
|
||||
responseType: 'stream',
|
||||
timeout: 60000, // 60 secondes timeout
|
||||
timeout: 60000,
|
||||
signal: controller.signal,
|
||||
headers: headers,
|
||||
// Configuration Axios pour la robustesse réseau
|
||||
validateStatus: function (status) {
|
||||
// Accept both 200 (full download) and 206 (partial content for resume)
|
||||
return (status >= 200 && status < 300) || status === 206;
|
||||
},
|
||||
// Retry configuration
|
||||
maxRedirects: 5,
|
||||
// Network resilience
|
||||
family: 4 // Force IPv4
|
||||
family: 4
|
||||
});
|
||||
|
||||
const contentLength = response.headers['content-length'];
|
||||
const totalSize = contentLength ? parseInt(contentLength, 10) + startByte : 0; // Adjust for resume
|
||||
let downloaded = startByte; // Start with existing bytes
|
||||
lastProgressTime = Date.now(); // Update time after response received
|
||||
const totalSize = contentLength ? parseInt(contentLength, 10) + startByte : 0;
|
||||
let downloaded = startByte;
|
||||
lastProgressTime = Date.now();
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check network status before attempting download
|
||||
@@ -344,8 +340,7 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
||||
});
|
||||
});
|
||||
|
||||
// Si on arrive ici, le téléchargement a réussi
|
||||
return;
|
||||
return dest;
|
||||
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
@@ -368,13 +363,16 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
||||
// If file is substantial size (> 1.5GB), treat as success and break
|
||||
if (sizeInMB >= 1500) {
|
||||
console.log('File appears to be complete despite error, treating as success');
|
||||
return; // Exit the retry loop successfully
|
||||
return dest; // Exit the retry loop successfully
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced file cleanup with validation
|
||||
if (fs.existsSync(dest)) {
|
||||
try {
|
||||
// HTTP 416 = Range Not Satisfiable, delete corrupted partial file
|
||||
const isRangeError = error.message && error.message.includes('416');
|
||||
|
||||
// Check if file is corrupted (small or invalid) or if error is non-resumable
|
||||
const partialStats = fs.statSync(dest);
|
||||
const isResumableError = error.message && (
|
||||
@@ -387,13 +385,14 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
||||
// Check if download appears to be complete (close to expected PWR size)
|
||||
const isPossiblyComplete = partialStats.size >= 1500 * 1024 * 1024; // >= 1.5GB
|
||||
|
||||
if (partialStats.size < 1024 * 1024 || (!isResumableError && !isPossiblyComplete)) {
|
||||
// Delete if file is too small OR error is non-resumable AND not possibly complete
|
||||
console.log(`[Cleanup] Removing PWR file (${!isResumableError && !isPossiblyComplete ? 'non-resumable error' : 'too small'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
if (isRangeError || partialStats.size < 1024 * 1024 || (!isResumableError && !isPossiblyComplete)) {
|
||||
// Delete if HTTP 416 OR file is too small OR error is non-resumable AND not possibly complete
|
||||
const reason = isRangeError ? 'HTTP 416 range error' : (!isResumableError && !isPossiblyComplete ? 'non-resumable error' : 'too small');
|
||||
console.log(`[Cleanup] Removing file (${reason}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
fs.unlinkSync(dest);
|
||||
} else {
|
||||
// Keep the file for resume on resumable errors or if possibly complete
|
||||
console.log(`[Resume] Keeping PWR file (${isPossiblyComplete ? 'possibly complete' : 'for resume'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(`[Resume] Keeping file (${isPossiblyComplete ? 'possibly complete' : 'for resume'}): ${(partialStats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.warn('Could not handle partial file:', cleanupError.message);
|
||||
@@ -554,6 +553,17 @@ async function retryDownload(url, dest, progressCallback, previousError = null)
|
||||
fs.mkdirSync(destDir, { recursive: true });
|
||||
}
|
||||
|
||||
// CRITICAL: Delete partial file before manual retry to avoid HTTP 416
|
||||
if (fs.existsSync(dest)) {
|
||||
try {
|
||||
const stats = fs.statSync(dest);
|
||||
console.log(`[Retry] Deleting partial file before retry: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
fs.unlinkSync(dest);
|
||||
} catch (err) {
|
||||
console.warn('Could not delete partial file:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await downloadFile(url, dest, progressCallback, additionalRetries);
|
||||
console.log('Manual retry successful');
|
||||
|
||||
15
main.js
15
main.js
@@ -1,4 +1,5 @@
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '.env') });
|
||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const fs = require('fs');
|
||||
@@ -446,6 +447,13 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
|
||||
|
||||
ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => {
|
||||
try {
|
||||
console.log(`[IPC] install-game called with parameters:`);
|
||||
console.log(` - playerName: ${playerName}`);
|
||||
console.log(` - javaPath: ${javaPath}`);
|
||||
console.log(` - installPath: ${installPath}`);
|
||||
console.log(` - branch: ${branch}`);
|
||||
console.log(`[IPC] branch type: ${typeof branch}, value: ${JSON.stringify(branch)}`);
|
||||
|
||||
// Signal installation start
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('installation-start');
|
||||
@@ -625,6 +633,7 @@ ipcMain.handle('load-close-launcher', () => {
|
||||
});
|
||||
|
||||
ipcMain.handle('select-install-path', async () => {
|
||||
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory'],
|
||||
title: 'Select Installation Folder'
|
||||
@@ -742,8 +751,8 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
||||
console.log('[IPC] Full JRE retry data:', JSON.stringify(retryData, null, 2));
|
||||
|
||||
const { retryJREDownload } = require('./backend/managers/javaManager');
|
||||
await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback);
|
||||
const jreCacheFile = path.join(retryData.cacheDir, retryData.fileName);
|
||||
await retryJREDownload(retryData.jreUrl, jreCacheFile, progressCallback);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
@@ -930,6 +939,10 @@ ipcMain.handle('get-local-app-data', async () => {
|
||||
return process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
||||
});
|
||||
|
||||
ipcMain.handle('get-env-var', async (event, key) => {
|
||||
return process.env[key];
|
||||
});
|
||||
|
||||
ipcMain.handle('get-user-id', async () => {
|
||||
try {
|
||||
const { getOrCreatePlayerId } = require('./backend/launcher');
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"adm-zip": "^0.5.10",
|
||||
"axios": "^1.6.0",
|
||||
"discord-rpc": "^4.0.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"electron-updater": "^6.7.3",
|
||||
"fs-extra": "^11.3.3",
|
||||
"tar": "^6.2.1",
|
||||
@@ -68,7 +69,8 @@
|
||||
"preload.js",
|
||||
"backend/**/*",
|
||||
"GUI/**/*",
|
||||
"package.json"
|
||||
"package.json",
|
||||
".env"
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
@@ -80,7 +82,7 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "build/icon.ico"
|
||||
"icon": "icon.ico"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
|
||||
@@ -35,6 +35,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
openGameLocation: () => ipcRenderer.invoke('open-game-location'),
|
||||
saveSettings: (settings) => ipcRenderer.invoke('save-settings', settings),
|
||||
loadSettings: () => ipcRenderer.invoke('load-settings'),
|
||||
getEnvVar: (key) => ipcRenderer.invoke('get-env-var', key),
|
||||
getLocalAppData: () => ipcRenderer.invoke('get-local-app-data'),
|
||||
getModsPath: () => ipcRenderer.invoke('get-mods-path'),
|
||||
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
|
||||
@@ -129,5 +130,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
},
|
||||
onUpdateDownloaded: (callback) => {
|
||||
ipcRenderer.on('update-downloaded', (event, data) => callback(data));
|
||||
},
|
||||
onUpdateError: (callback) => {
|
||||
ipcRenderer.on('update-error', (event, data) => callback(data));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user