mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 16:21:49 -03:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
42
.github/ISSUE_TEMPLATE/translation_request.yml
vendored
Normal file
42
.github/ISSUE_TEMPLATE/translation_request.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Translation Request
|
||||||
|
description: Request translation for text or content
|
||||||
|
title: "[TRANSLATION] "
|
||||||
|
labels: ["translation"]
|
||||||
|
body:
|
||||||
|
- type: input
|
||||||
|
id: language
|
||||||
|
attributes:
|
||||||
|
label: Target Language
|
||||||
|
description: What language do you want to translate to?
|
||||||
|
placeholder: "e.g. Spanish (es-ES), French (fr-FR)"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: source_text
|
||||||
|
attributes:
|
||||||
|
label: Source Text
|
||||||
|
description: The original text that needs to be translated.
|
||||||
|
placeholder: "Paste the text here..."
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Context
|
||||||
|
description: Provide context about where this text appears or how it's used.
|
||||||
|
placeholder: "This text appears in..., It's used for..."
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: file_location
|
||||||
|
attributes:
|
||||||
|
label: File Location
|
||||||
|
description: Where is this text located in the codebase?
|
||||||
|
placeholder: "e.g. src/components/Button.js:15"
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: notes
|
||||||
|
attributes:
|
||||||
|
label: Additional Notes
|
||||||
|
description: Any specific instructions or notes for the translator.
|
||||||
32
.github/workflows/release.yml
vendored
32
.github/workflows/release.yml
vendored
@@ -25,6 +25,14 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
|
|
||||||
|
- name: Create .env file
|
||||||
|
env:
|
||||||
|
CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||||
|
DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
|
run: |
|
||||||
|
echo "CURSEFORGE_API_KEY=$CF_KEY" > .env
|
||||||
|
echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env
|
||||||
|
|
||||||
- name: Build Linux Packages
|
- name: Build Linux Packages
|
||||||
run: |
|
run: |
|
||||||
npx electron-builder --linux --x64 --arm64 --publish never
|
npx electron-builder --linux --x64 --arm64 --publish never
|
||||||
@@ -48,7 +56,17 @@ jobs:
|
|||||||
node-version: '22'
|
node-version: '22'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npx electron-builder --win --publish never
|
|
||||||
|
- name: Create .env file
|
||||||
|
env:
|
||||||
|
CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||||
|
DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
|
run: |
|
||||||
|
echo "CURSEFORGE_API_KEY=$CF_KEY" > .env
|
||||||
|
echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env
|
||||||
|
|
||||||
|
- name: Build Windows Packages
|
||||||
|
run: npx electron-builder --win --publish never
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: windows-builds
|
name: windows-builds
|
||||||
@@ -66,7 +84,17 @@ jobs:
|
|||||||
node-version: '22'
|
node-version: '22'
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npx electron-builder --mac --publish never
|
|
||||||
|
- name: Create .env file
|
||||||
|
env:
|
||||||
|
CF_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
|
||||||
|
DISCORD_ID: ${{ secrets.DISCORD_CLIENT_ID }}
|
||||||
|
run: |
|
||||||
|
echo "CURSEFORGE_API_KEY=$CF_KEY" > .env
|
||||||
|
echo "DISCORD_CLIENT_ID=$DISCORD_ID" >> .env
|
||||||
|
|
||||||
|
- name: Build Windows Packages
|
||||||
|
run: npx electron-builder --mac --publish never
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-builds
|
name: macos-builds
|
||||||
|
|||||||
@@ -619,9 +619,17 @@
|
|||||||
<div id="progressErrorMessage" class="progress-error-message"></div>
|
<div id="progressErrorMessage" class="progress-error-message"></div>
|
||||||
<div class="progress-retry-section">
|
<div class="progress-retry-section">
|
||||||
<span id="progressRetryInfo" class="progress-retry-info"></span>
|
<span id="progressRetryInfo" class="progress-retry-info"></span>
|
||||||
<button id="progressRetryBtn" class="progress-retry-btn" style="display: none;">
|
<div class="progress-retry-buttons">
|
||||||
🔄 Retry Download
|
<button id="progressJRRetryBtn" class="progress-retry-btn" style="display: none;">
|
||||||
|
Retry Java Download
|
||||||
</button>
|
</button>
|
||||||
|
<button id="progressPWRRetryBtn" class="progress-retry-btn" style="display: none;">
|
||||||
|
Retry Game Download
|
||||||
|
</button>
|
||||||
|
<button id="progressRetryBtn" class="progress-retry-btn" style="display: none;">
|
||||||
|
Retry Download
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -777,7 +785,11 @@
|
|||||||
<a href="https://github.com/ericiskoolbeans" target="_blank"
|
<a href="https://github.com/ericiskoolbeans" target="_blank"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
||||||
<a href="https://github.com/fazrigading" target="_blank"
|
<a href="https://github.com/fazrigading" target="_blank"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>
|
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>,
|
||||||
|
<a href="https://github.com/Rahul-Sahani04" target="_blank"
|
||||||
|
class="text-blue-400 hover:text-blue-300 transition-colors">@Rahul-Sahani04</a>,
|
||||||
|
<a href="https://github.com/xSamiVS" target="_blank"
|
||||||
|
class="text-blue-400 hover:text-blue-300 transition-colors">@xSamiVS</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -846,6 +858,7 @@
|
|||||||
<script src="js/i18n.js"></script>
|
<script src="js/i18n.js"></script>
|
||||||
<script type="module" src="js/settings.js"></script>
|
<script type="module" src="js/settings.js"></script>
|
||||||
<script type="module" src="js/update.js"></script>
|
<script type="module" src="js/update.js"></script>
|
||||||
|
<script src="js/updater.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ const i18n = (() => {
|
|||||||
let translations = {};
|
let translations = {};
|
||||||
const availableLanguages = [
|
const availableLanguages = [
|
||||||
{ code: 'en', name: 'English' },
|
{ code: 'en', name: 'English' },
|
||||||
{ code: 'es', name: 'Español' },
|
{ code: 'es-ES', name: 'Español (España)' },
|
||||||
{ code: 'pt-BR', name: 'Português (Brasil)' }
|
{ code: 'pt-BR', name: 'Portuguese (Brazil)' },
|
||||||
|
{ code: 'tr-TR', name: 'Turkish (Turkey)' }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Load single language file
|
// Load single language file
|
||||||
|
|||||||
@@ -200,10 +200,15 @@ async function loadBrowseMods() {
|
|||||||
browseContainer.innerHTML = `
|
browseContainer.innerHTML = `
|
||||||
<div class=\"empty-browse-mods\">
|
<div class=\"empty-browse-mods\">
|
||||||
<i class=\"fas fa-key\"></i>
|
<i class=\"fas fa-key\"></i>
|
||||||
<h4>API Key Required</h4>
|
<h4 data-i18n="mods.apiKeyRequired">API Key Required</h4>
|
||||||
<p>CurseForge API key is needed to browse mods</p>
|
<p data-i18n="mods.apiKeyRequiredDesc">CurseForge API key is needed to browse mods</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
if (window.i18n) {
|
||||||
|
const container = modsContainer.querySelector('.empty-browse-mods');
|
||||||
|
container.querySelector('h4').textContent = window.i18n.t('mods.apiKeyRequired');
|
||||||
|
container.querySelector('p').textContent = window.i18n.t('mods.apiKeyRequiredDesc');
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
216
GUI/js/ui.js
216
GUI/js/ui.js
@@ -10,6 +10,8 @@ let progressErrorContainer;
|
|||||||
let progressErrorMessage;
|
let progressErrorMessage;
|
||||||
let progressRetryInfo;
|
let progressRetryInfo;
|
||||||
let progressRetryBtn;
|
let progressRetryBtn;
|
||||||
|
let progressJRRetryBtn;
|
||||||
|
let progressPWRRetryBtn;
|
||||||
|
|
||||||
// Download retry state
|
// Download retry state
|
||||||
let currentDownloadState = {
|
let currentDownloadState = {
|
||||||
@@ -199,7 +201,8 @@ function updateProgress(data) {
|
|||||||
if ((data.error || (data.message && data.message.includes('failed'))) &&
|
if ((data.error || (data.message && data.message.includes('failed'))) &&
|
||||||
!(data.retryState && data.retryState.isAutomaticRetry)) {
|
!(data.retryState && data.retryState.isAutomaticRetry)) {
|
||||||
const errorType = categorizeError(data.message);
|
const errorType = categorizeError(data.message);
|
||||||
showDownloadError(data.message, data.canRetry, errorType);
|
console.log('[UI] Showing download error:', { message: data.message, canRetry: data.canRetry, errorType });
|
||||||
|
showDownloadError(data.message, data.canRetry, errorType, data);
|
||||||
} else if (data.percent === 100) {
|
} else if (data.percent === 100) {
|
||||||
hideDownloadError();
|
hideDownloadError();
|
||||||
} else if (data.retryState && data.retryState.isAutomaticRetry) {
|
} else if (data.retryState && data.retryState.isAutomaticRetry) {
|
||||||
@@ -230,8 +233,16 @@ function updateRetryState(retryState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDownloadError(errorMessage, canRetry = true, errorType = 'general') {
|
function showDownloadError(errorMessage, canRetry = true, errorType = 'general', data = null) {
|
||||||
if (!progressErrorContainer || !progressErrorMessage || !progressRetryBtn) return;
|
if (!progressErrorContainer || !progressErrorMessage) return;
|
||||||
|
|
||||||
|
console.log('[UI] showDownloadError called with:', { errorMessage, canRetry, errorType, data });
|
||||||
|
console.log('[UI] Data properties:', {
|
||||||
|
hasData: !!data,
|
||||||
|
hasRetryData: !!(data && data.retryData),
|
||||||
|
dataErrorType: data && data.errorType,
|
||||||
|
dataIsJREError: data && data.retryData && data.retryData.isJREError
|
||||||
|
});
|
||||||
|
|
||||||
currentDownloadState.lastError = errorMessage;
|
currentDownloadState.lastError = errorMessage;
|
||||||
currentDownloadState.canRetry = canRetry;
|
currentDownloadState.canRetry = canRetry;
|
||||||
@@ -242,13 +253,37 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general')
|
|||||||
currentDownloadState.branch = data.retryData.branch;
|
currentDownloadState.branch = data.retryData.branch;
|
||||||
currentDownloadState.fileName = data.retryData.fileName;
|
currentDownloadState.fileName = data.retryData.fileName;
|
||||||
currentDownloadState.cacheDir = data.retryData.cacheDir;
|
currentDownloadState.cacheDir = data.retryData.cacheDir;
|
||||||
|
// Override errorType if specified in data
|
||||||
|
if (data.errorType) {
|
||||||
|
currentDownloadState.errorType = data.errorType;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide all retry buttons first
|
||||||
|
if (progressRetryBtn) progressRetryBtn.style.display = 'none';
|
||||||
|
if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none';
|
||||||
|
if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none';
|
||||||
|
|
||||||
// User-friendly error messages
|
// User-friendly error messages
|
||||||
const userMessage = getErrorMessage(errorMessage, errorType);
|
const userMessage = getErrorMessage(errorMessage, errorType);
|
||||||
progressErrorMessage.textContent = userMessage;
|
progressErrorMessage.textContent = userMessage;
|
||||||
progressErrorContainer.style.display = 'block';
|
progressErrorContainer.style.display = 'block';
|
||||||
progressRetryBtn.style.display = canRetry ? 'block' : 'none';
|
|
||||||
|
// Show appropriate retry button based on error type
|
||||||
|
if (canRetry) {
|
||||||
|
if (errorType === 'jre') {
|
||||||
|
if (progressJRRetryBtn) {
|
||||||
|
console.log('[UI] Showing JRE retry button');
|
||||||
|
progressJRRetryBtn.style.display = 'block';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// All other errors use PWR retry button (game download, butler, etc.)
|
||||||
|
if (progressPWRRetryBtn) {
|
||||||
|
console.log('[UI] Showing PWR retry button');
|
||||||
|
progressPWRRetryBtn.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add visual indicators based on error type
|
// Add visual indicators based on error type
|
||||||
progressErrorContainer.className = `progress-error-container error-${errorType}`;
|
progressErrorContainer.className = `progress-error-container error-${errorType}`;
|
||||||
@@ -261,6 +296,11 @@ function showDownloadError(errorMessage, canRetry = true, errorType = 'general')
|
|||||||
function hideDownloadError() {
|
function hideDownloadError() {
|
||||||
if (!progressErrorContainer) return;
|
if (!progressErrorContainer) return;
|
||||||
|
|
||||||
|
// Hide all retry buttons
|
||||||
|
if (progressRetryBtn) progressRetryBtn.style.display = 'none';
|
||||||
|
if (progressJRRetryBtn) progressJRRetryBtn.style.display = 'none';
|
||||||
|
if (progressPWRRetryBtn) progressPWRRetryBtn.style.display = 'none';
|
||||||
|
|
||||||
progressErrorContainer.style.display = 'none';
|
progressErrorContainer.style.display = 'none';
|
||||||
currentDownloadState.canRetry = false;
|
currentDownloadState.canRetry = false;
|
||||||
currentDownloadState.lastError = null;
|
currentDownloadState.lastError = null;
|
||||||
@@ -589,6 +629,8 @@ function setupUI() {
|
|||||||
progressErrorMessage = document.getElementById('progressErrorMessage');
|
progressErrorMessage = document.getElementById('progressErrorMessage');
|
||||||
progressRetryInfo = document.getElementById('progressRetryInfo');
|
progressRetryInfo = document.getElementById('progressRetryInfo');
|
||||||
progressRetryBtn = document.getElementById('progressRetryBtn');
|
progressRetryBtn = document.getElementById('progressRetryBtn');
|
||||||
|
progressJRRetryBtn = document.getElementById('progressJRRetryBtn');
|
||||||
|
progressPWRRetryBtn = document.getElementById('progressPWRRetryBtn');
|
||||||
|
|
||||||
// Setup draggable progress bar
|
// Setup draggable progress bar
|
||||||
setupProgressDrag();
|
setupProgressDrag();
|
||||||
@@ -784,6 +826,8 @@ function categorizeError(message) {
|
|||||||
return 'space';
|
return 'space';
|
||||||
} else if (msg.includes('conflict') || msg.includes('already exists')) {
|
} else if (msg.includes('conflict') || msg.includes('already exists')) {
|
||||||
return 'conflict';
|
return 'conflict';
|
||||||
|
} else if (msg.includes('jre') || msg.includes('java runtime')) {
|
||||||
|
return 'jre';
|
||||||
} else {
|
} else {
|
||||||
return 'general';
|
return 'general';
|
||||||
}
|
}
|
||||||
@@ -812,6 +856,8 @@ function getErrorMessage(technicalMessage, errorType) {
|
|||||||
return 'Insufficient disk space. Free up space and retry.';
|
return 'Insufficient disk space. Free up space and retry.';
|
||||||
case 'conflict':
|
case 'conflict':
|
||||||
return 'Installation directory conflict. Please retry.';
|
return 'Installation directory conflict. Please retry.';
|
||||||
|
case 'jre':
|
||||||
|
return 'Java runtime download failed. Please retry.';
|
||||||
default:
|
default:
|
||||||
return 'Download failed. Please retry.';
|
return 'Download failed. Please retry.';
|
||||||
}
|
}
|
||||||
@@ -839,53 +885,173 @@ function updateConnectionQuality(quality) {
|
|||||||
|
|
||||||
// Enhanced retry button setup
|
// Enhanced retry button setup
|
||||||
function setupRetryButton() {
|
function setupRetryButton() {
|
||||||
if (!progressRetryBtn) return;
|
// Setup JRE retry button
|
||||||
|
if (progressJRRetryBtn) {
|
||||||
progressRetryBtn.addEventListener('click', async () => {
|
progressJRRetryBtn.addEventListener('click', async () => {
|
||||||
if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) {
|
if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
progressJRRetryBtn.disabled = true;
|
||||||
// Disable retry button during retry
|
progressJRRetryBtn.textContent = 'Retrying...';
|
||||||
progressRetryBtn.disabled = true;
|
progressJRRetryBtn.classList.add('retrying');
|
||||||
progressRetryBtn.textContent = '🔄 Retrying...';
|
currentDownloadState.isDownloading = true;
|
||||||
progressRetryBtn.classList.add('retrying');
|
|
||||||
|
try {
|
||||||
|
hideDownloadError();
|
||||||
|
|
||||||
|
if (progressRetryInfo) {
|
||||||
|
progressRetryInfo.style.background = '';
|
||||||
|
progressRetryInfo.style.color = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressText) {
|
||||||
|
progressText.textContent = 'Re-downloading Java runtime...';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentDownloadState.retryData || currentDownloadState.errorType !== 'jre') {
|
||||||
|
currentDownloadState.retryData = {
|
||||||
|
isJREError: true,
|
||||||
|
jreUrl: '',
|
||||||
|
fileName: 'jre.tar.gz',
|
||||||
|
cacheDir: '',
|
||||||
|
osName: 'linux',
|
||||||
|
arch: 'amd64'
|
||||||
|
};
|
||||||
|
console.log('[UI] Created default JRE retry data:', currentDownloadState.retryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.electronAPI && window.electronAPI.retryDownload) {
|
||||||
|
const result = await window.electronAPI.retryDownload(currentDownloadState.retryData);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'JRE retry failed');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('electronAPI.retryDownload not available, simulating JRE retry...');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
throw new Error('JRE retry API not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('JRE retry failed:', error);
|
||||||
|
showDownloadError(`JRE retry failed: ${error.message}`, true, 'jre');
|
||||||
|
} finally {
|
||||||
|
if (progressJRRetryBtn) {
|
||||||
|
progressJRRetryBtn.disabled = false;
|
||||||
|
progressJRRetryBtn.textContent = 'Retry Java Download';
|
||||||
|
progressJRRetryBtn.classList.remove('retrying');
|
||||||
|
}
|
||||||
|
currentDownloadState.isDownloading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup PWR retry button
|
||||||
|
if (progressPWRRetryBtn) {
|
||||||
|
progressPWRRetryBtn.addEventListener('click', async () => {
|
||||||
|
if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressPWRRetryBtn.disabled = true;
|
||||||
|
progressPWRRetryBtn.textContent = 'Retrying...';
|
||||||
|
progressPWRRetryBtn.classList.add('retrying');
|
||||||
currentDownloadState.isDownloading = true;
|
currentDownloadState.isDownloading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Hide error state during retry
|
|
||||||
hideDownloadError();
|
hideDownloadError();
|
||||||
|
|
||||||
// Reset retry info styling for manual retries
|
|
||||||
if (progressRetryInfo) {
|
if (progressRetryInfo) {
|
||||||
progressRetryInfo.style.background = '';
|
progressRetryInfo.style.background = '';
|
||||||
progressRetryInfo.style.color = '';
|
progressRetryInfo.style.color = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress text with context-aware message
|
|
||||||
if (progressText) {
|
if (progressText) {
|
||||||
const contextMessage = getRetryContextMessage();
|
const contextMessage = getRetryContextMessage();
|
||||||
progressText.textContent = contextMessage;
|
progressText.textContent = contextMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure retry data exists, create defaults if null
|
if (!currentDownloadState.retryData || currentDownloadState.errorType === 'jre') {
|
||||||
if (!currentDownloadState.retryData) {
|
|
||||||
currentDownloadState.retryData = {
|
currentDownloadState.retryData = {
|
||||||
branch: 'release',
|
branch: 'release',
|
||||||
fileName: '4.pwr'
|
fileName: '4.pwr'
|
||||||
};
|
};
|
||||||
|
console.log('[UI] Created default PWR retry data:', currentDownloadState.retryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.electronAPI && window.electronAPI.retryDownload) {
|
||||||
|
const result = await window.electronAPI.retryDownload(currentDownloadState.retryData);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Game retry failed');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('electronAPI.retryDownload not available, simulating PWR retry...');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
throw new Error('Game retry API not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PWR retry failed:', error);
|
||||||
|
const errorType = categorizeError(error.message);
|
||||||
|
showDownloadError(`Game retry failed: ${error.message}`, true, errorType, error);
|
||||||
|
} finally {
|
||||||
|
if (progressPWRRetryBtn) {
|
||||||
|
progressPWRRetryBtn.disabled = false;
|
||||||
|
progressPWRRetryBtn.textContent = error && error.isJREError ? 'Retry Java Download' : 'Retry Game Download';
|
||||||
|
progressPWRRetryBtn.classList.remove('retrying');
|
||||||
|
}
|
||||||
|
currentDownloadState.isDownloading = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup generic retry button (fallback)
|
||||||
|
if (progressRetryBtn) {
|
||||||
|
progressRetryBtn.addEventListener('click', async () => {
|
||||||
|
if (!currentDownloadState.canRetry || currentDownloadState.isDownloading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
progressRetryBtn.disabled = true;
|
||||||
|
progressRetryBtn.textContent = 'Retrying...';
|
||||||
|
progressRetryBtn.classList.add('retrying');
|
||||||
|
currentDownloadState.isDownloading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
hideDownloadError();
|
||||||
|
|
||||||
|
if (progressRetryInfo) {
|
||||||
|
progressRetryInfo.style.background = '';
|
||||||
|
progressRetryInfo.style.color = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressText) {
|
||||||
|
const contextMessage = getRetryContextMessage();
|
||||||
|
progressText.textContent = contextMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentDownloadState.retryData) {
|
||||||
|
if (currentDownloadState.errorType === 'jre') {
|
||||||
|
currentDownloadState.retryData = {
|
||||||
|
isJREError: true,
|
||||||
|
jreUrl: '',
|
||||||
|
fileName: 'jre.tar.gz',
|
||||||
|
cacheDir: '',
|
||||||
|
osName: 'linux',
|
||||||
|
arch: 'amd64'
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
currentDownloadState.retryData = {
|
||||||
|
branch: 'release',
|
||||||
|
fileName: '4.pwr'
|
||||||
|
};
|
||||||
|
}
|
||||||
console.log('[UI] Created default retry data:', currentDownloadState.retryData);
|
console.log('[UI] Created default retry data:', currentDownloadState.retryData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send retry request to backend
|
|
||||||
if (window.electronAPI && window.electronAPI.retryDownload) {
|
if (window.electronAPI && window.electronAPI.retryDownload) {
|
||||||
const result = await window.electronAPI.retryDownload(currentDownloadState.retryData);
|
const result = await window.electronAPI.retryDownload(currentDownloadState.retryData);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.error || 'Retry failed');
|
throw new Error(result.error || 'Retry failed');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback for development/testing
|
|
||||||
console.warn('electronAPI.retryDownload not available, simulating retry...');
|
console.warn('electronAPI.retryDownload not available, simulating retry...');
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
throw new Error('Retry API not available');
|
throw new Error('Retry API not available');
|
||||||
@@ -895,15 +1061,17 @@ function setupRetryButton() {
|
|||||||
console.error('Retry failed:', error);
|
console.error('Retry failed:', error);
|
||||||
const errorType = categorizeError(error.message);
|
const errorType = categorizeError(error.message);
|
||||||
showDownloadError(`Retry failed: ${error.message}`, true, errorType);
|
showDownloadError(`Retry failed: ${error.message}`, true, errorType);
|
||||||
|
} finally {
|
||||||
// Reset retry button
|
if (progressRetryBtn) {
|
||||||
progressRetryBtn.disabled = false;
|
progressRetryBtn.disabled = false;
|
||||||
progressRetryBtn.textContent = '🔄 Retry Download';
|
progressRetryBtn.textContent = 'Retry Download';
|
||||||
progressRetryBtn.classList.remove('retrying');
|
progressRetryBtn.classList.remove('retrying');
|
||||||
|
}
|
||||||
currentDownloadState.isDownloading = false;
|
currentDownloadState.isDownloading = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getRetryContextMessage() {
|
function getRetryContextMessage() {
|
||||||
const errorType = currentDownloadState.errorType;
|
const errorType = currentDownloadState.errorType;
|
||||||
@@ -925,6 +1093,8 @@ function getRetryContextMessage() {
|
|||||||
return 'Retrying with corrected permissions...';
|
return 'Retrying with corrected permissions...';
|
||||||
case 'conflict':
|
case 'conflict':
|
||||||
return 'Retrying after resolving conflicts...';
|
return 'Retrying after resolving conflicts...';
|
||||||
|
case 'jre':
|
||||||
|
return 'Re-downloading Java runtime...';
|
||||||
default:
|
default:
|
||||||
return 'Initiating retry download...';
|
return 'Initiating retry download...';
|
||||||
}
|
}
|
||||||
|
|||||||
149
GUI/js/updater.js
Normal file
149
GUI/js/updater.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Launcher Update Manager UI
|
||||||
|
|
||||||
|
let updateModal = null;
|
||||||
|
let downloadProgressBar = null;
|
||||||
|
|
||||||
|
function initUpdater() {
|
||||||
|
// Listen for update events from main process
|
||||||
|
if (window.electronAPI && window.electronAPI.onUpdateAvailable) {
|
||||||
|
window.electronAPI.onUpdateAvailable((updateInfo) => {
|
||||||
|
showUpdateModal(updateInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.electronAPI && window.electronAPI.onUpdateDownloadProgress) {
|
||||||
|
window.electronAPI.onUpdateDownloadProgress((progress) => {
|
||||||
|
updateDownloadProgress(progress);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.electronAPI && window.electronAPI.onUpdateDownloaded) {
|
||||||
|
window.electronAPI.onUpdateDownloaded((info) => {
|
||||||
|
showInstallUpdatePrompt(info);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showUpdateModal(updateInfo) {
|
||||||
|
if (updateModal) {
|
||||||
|
updateModal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModal = document.createElement('div');
|
||||||
|
updateModal.className = 'update-modal-overlay';
|
||||||
|
updateModal.innerHTML = `
|
||||||
|
<div class="update-modal">
|
||||||
|
<div class="update-header">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
<h2>Launcher Update Available</h2>
|
||||||
|
</div>
|
||||||
|
<div class="update-content">
|
||||||
|
<p class="update-version">Version ${updateInfo.newVersion} is available!</p>
|
||||||
|
<p class="current-version">Current version: ${updateInfo.currentVersion}</p>
|
||||||
|
${updateInfo.releaseNotes ? `<div class="release-notes">${updateInfo.releaseNotes}</div>` : ''}
|
||||||
|
</div>
|
||||||
|
<div class="update-progress" style="display: none;">
|
||||||
|
<div class="progress-bar-container">
|
||||||
|
<div class="progress-bar" id="updateProgressBar"></div>
|
||||||
|
</div>
|
||||||
|
<p class="progress-text" id="updateProgressText">Downloading...</p>
|
||||||
|
</div>
|
||||||
|
<div class="update-actions">
|
||||||
|
<button class="btn-primary" onclick="downloadUpdate()">
|
||||||
|
<i class="fas fa-download"></i> Download Update
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(updateModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadUpdate() {
|
||||||
|
const downloadBtn = updateModal.querySelector('.btn-primary');
|
||||||
|
const progressDiv = updateModal.querySelector('.update-progress');
|
||||||
|
|
||||||
|
// Disable button and show progress
|
||||||
|
downloadBtn.disabled = true;
|
||||||
|
progressDiv.style.display = 'block';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await window.electronAPI.downloadUpdate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download update:', error);
|
||||||
|
alert('Failed to download update. Please try again later.');
|
||||||
|
dismissUpdateModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDownloadProgress(progress) {
|
||||||
|
if (!updateModal) return;
|
||||||
|
|
||||||
|
const progressBar = document.getElementById('updateProgressBar');
|
||||||
|
const progressText = document.getElementById('updateProgressText');
|
||||||
|
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.style.width = `${progress.percent}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressText) {
|
||||||
|
const mbTransferred = (progress.transferred / 1024 / 1024).toFixed(2);
|
||||||
|
const mbTotal = (progress.total / 1024 / 1024).toFixed(2);
|
||||||
|
const speed = (progress.bytesPerSecond / 1024 / 1024).toFixed(2);
|
||||||
|
progressText.textContent = `Downloading... ${mbTransferred}MB / ${mbTotal}MB (${speed} MB/s)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInstallUpdatePrompt(info) {
|
||||||
|
if (updateModal) {
|
||||||
|
updateModal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModal = document.createElement('div');
|
||||||
|
updateModal.className = 'update-modal-overlay';
|
||||||
|
updateModal.innerHTML = `
|
||||||
|
<div class="update-modal">
|
||||||
|
<div class="update-header">
|
||||||
|
<i class="fas fa-check-circle"></i>
|
||||||
|
<h2>Update Downloaded</h2>
|
||||||
|
</div>
|
||||||
|
<div class="update-content">
|
||||||
|
<p>Version ${info.version} has been downloaded and is ready to install.</p>
|
||||||
|
<p class="update-note">The launcher will restart to complete the installation.</p>
|
||||||
|
</div>
|
||||||
|
<div class="update-actions">
|
||||||
|
<button class="btn-primary" onclick="installUpdate()">
|
||||||
|
<i class="fas fa-sync-alt"></i> Restart & Install
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(updateModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function installUpdate() {
|
||||||
|
try {
|
||||||
|
await window.electronAPI.installUpdate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to install update:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismissUpdateModal() {
|
||||||
|
if (updateModal) {
|
||||||
|
updateModal.remove();
|
||||||
|
updateModal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', initUpdater);
|
||||||
|
|
||||||
|
// Export functions
|
||||||
|
window.UpdaterUI = {
|
||||||
|
showUpdateModal,
|
||||||
|
dismissUpdateModal,
|
||||||
|
downloadUpdate,
|
||||||
|
installUpdate
|
||||||
|
};
|
||||||
@@ -57,7 +57,9 @@
|
|||||||
"noDescription": "No description available",
|
"noDescription": "No description available",
|
||||||
"confirmDelete": "Are you sure you want to delete \"{name}\"?",
|
"confirmDelete": "Are you sure you want to delete \"{name}\"?",
|
||||||
"confirmDeleteDesc": "This action cannot be undone.",
|
"confirmDeleteDesc": "This action cannot be undone.",
|
||||||
"confirmDeletion": "Confirm Deletion"
|
"confirmDeletion": "Confirm Deletion",
|
||||||
|
"apiKeyRequired": "API Key Required",
|
||||||
|
"apiKeyRequiredDesc": "CurseForge API key is needed to browse mods"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"title": "ALL NEWS",
|
"title": "ALL NEWS",
|
||||||
|
|||||||
@@ -14,9 +14,11 @@
|
|||||||
"install": {
|
"install": {
|
||||||
"title": "LAUNCHER GRATUITO",
|
"title": "LAUNCHER GRATUITO",
|
||||||
"playerName": "Nombre del Jugador",
|
"playerName": "Nombre del Jugador",
|
||||||
"playerNamePlaceholder": "Ingresa tu nombre", "gameBranch": "Versión del Juego",
|
"playerNamePlaceholder": "Ingresa tu nombre",
|
||||||
|
"gameBranch": "Versión del Juego",
|
||||||
"releaseVersion": "Lanzamiento (Estable)",
|
"releaseVersion": "Lanzamiento (Estable)",
|
||||||
"preReleaseVersion": "Pre-Lanzamiento (Experimental)", "customInstallation": "Instalación Personalizada",
|
"preReleaseVersion": "Pre-Lanzamiento (Experimental)",
|
||||||
|
"customInstallation": "Instalación Personalizada",
|
||||||
"installationFolder": "Carpeta de Instalación",
|
"installationFolder": "Carpeta de Instalación",
|
||||||
"pathPlaceholder": "Ubicación predeterminada",
|
"pathPlaceholder": "Ubicación predeterminada",
|
||||||
"browse": "Examinar",
|
"browse": "Examinar",
|
||||||
@@ -55,7 +57,9 @@
|
|||||||
"noDescription": "Sin descripción disponible",
|
"noDescription": "Sin descripción disponible",
|
||||||
"confirmDelete": "¿Estás seguro de que quieres eliminar \"{name}\"?",
|
"confirmDelete": "¿Estás seguro de que quieres eliminar \"{name}\"?",
|
||||||
"confirmDeleteDesc": "Esta acción no se puede deshacer.",
|
"confirmDeleteDesc": "Esta acción no se puede deshacer.",
|
||||||
"confirmDeletion": "Confirmar eliminación"
|
"confirmDeletion": "Confirmar eliminación",
|
||||||
|
"apiKeyRequired": "Clave API Requerida",
|
||||||
|
"apiKeyRequiredDesc": "Se necesita una clave API de CurseForge para explorar mods"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"title": "TODAS LAS NOTICIAS",
|
"title": "TODAS LAS NOTICIAS",
|
||||||
@@ -55,7 +55,9 @@
|
|||||||
"noDescription": "Nenhuma descrição disponível",
|
"noDescription": "Nenhuma descrição disponível",
|
||||||
"confirmDelete": "Tem certeza de que deseja excluir \"{name}\"?",
|
"confirmDelete": "Tem certeza de que deseja excluir \"{name}\"?",
|
||||||
"confirmDeleteDesc": "Esta ação não pode ser desfeita.",
|
"confirmDeleteDesc": "Esta ação não pode ser desfeita.",
|
||||||
"confirmDeletion": "Confirmar exclusão"
|
"confirmDeletion": "Confirmar exclusão",
|
||||||
|
"apiKeyRequired": "Chave de API Necessária",
|
||||||
|
"apiKeyRequiredDesc": "Chave de API do CurseForge é necessária para procurar mods"
|
||||||
},
|
},
|
||||||
"news": {
|
"news": {
|
||||||
"title": "TODAS AS NOTÍCIAS",
|
"title": "TODAS AS NOTÍCIAS",
|
||||||
|
|||||||
246
GUI/locales/tr-TR.json
Normal file
246
GUI/locales/tr-TR.json
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
{
|
||||||
|
"nav": {
|
||||||
|
"play": "Oyna",
|
||||||
|
"mods": "Modlar",
|
||||||
|
"news": "Haberler",
|
||||||
|
"chat": "Oyuncu Sohbeti",
|
||||||
|
"settings": "Ayarlar"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"playersLabel": "Oyuncular:",
|
||||||
|
"manageProfiles": "Profilleri Yönet",
|
||||||
|
"defaultProfile": "Varsayılan"
|
||||||
|
},
|
||||||
|
"install": {
|
||||||
|
"title": "ÜCRETSİZ OYNA BAŞLATICI",
|
||||||
|
"playerName": "Oyuncu Adı",
|
||||||
|
"playerNamePlaceholder": "Adınızı girin",
|
||||||
|
"gameBranch": "Oyun Sürümü",
|
||||||
|
"releaseVersion": "Yayın (Stabil)",
|
||||||
|
"preReleaseVersion": "Ön-Yayın (Deneysel)",
|
||||||
|
"customInstallation": "Özel Kurulum",
|
||||||
|
"installationFolder": "Kurulum Klasörü",
|
||||||
|
"pathPlaceholder": "Varsayılan konum",
|
||||||
|
"browse": "Gözat",
|
||||||
|
"installButton": "HYTALE KURU",
|
||||||
|
"installing": "KURULUYOR..."
|
||||||
|
},
|
||||||
|
"play": {
|
||||||
|
"ready": "OYNAMAYA HAZIR",
|
||||||
|
"subtitle": "Hytale'i başlat ve maceraya başla",
|
||||||
|
"playButton": "HYTALE'YI OYNA",
|
||||||
|
"latestNews": "SON HABERLER",
|
||||||
|
"viewAll": "HEPSINI GÖR",
|
||||||
|
"checking": "KONTROL EDİLİYOR...",
|
||||||
|
"play": "OYNA"
|
||||||
|
},
|
||||||
|
"mods": {
|
||||||
|
"searchPlaceholder": "Modları ara...",
|
||||||
|
"myMods": "BENİM MODLARIM",
|
||||||
|
"previous": "ÖNCEKİ",
|
||||||
|
"next": "SONRAKİ",
|
||||||
|
"page": "Sayfa",
|
||||||
|
"of": "nın",
|
||||||
|
"modalTitle": "BENİM MODLARIM",
|
||||||
|
"noModsFound": "Mod Bulunamadı",
|
||||||
|
"noModsFoundDesc": "Aramanızı ayarlamayı deneyin",
|
||||||
|
"noModsInstalled": "Hiçbir Mod Kurulu Değil",
|
||||||
|
"noModsInstalledDesc": "CurseForge'dan modlar ekleyin veya yerel dosyalar içe aktarın",
|
||||||
|
"view": "GÖR",
|
||||||
|
"install": "KURU",
|
||||||
|
"installed": "KURULU",
|
||||||
|
"enable": "ETKİNLEŞTİR",
|
||||||
|
"disable": "DEĞİ",
|
||||||
|
"active": "AKTİF",
|
||||||
|
"disabled": "DEĞİ",
|
||||||
|
"delete": "Modı sil",
|
||||||
|
"noDescription": "Açıklama yok",
|
||||||
|
"confirmDelete": "\"{name}\" öğesini silmek istediğinizden emin misiniz?",
|
||||||
|
"confirmDeleteDesc": "Bu işlem geri alınamaz.",
|
||||||
|
"confirmDeletion": "Silmeyi Onayla",
|
||||||
|
"apiKeyRequired": "API Anahtarı Gerekli",
|
||||||
|
"apiKeyRequiredDesc": "Modlara göz atmak için CurseForge API anahtarı gereklidir"
|
||||||
|
},
|
||||||
|
"news": {
|
||||||
|
"title": "TÜM HABERLER",
|
||||||
|
"readMore": "Daha Fazla Oku"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"title": "OYUNCU SOHBETI",
|
||||||
|
"pickColor": "Renk",
|
||||||
|
"inputPlaceholder": "Mesajınızı yazın...",
|
||||||
|
"send": "Gönder",
|
||||||
|
"online": "çevrimiçi",
|
||||||
|
"charCounter": "{current}/{max}",
|
||||||
|
"secureChat": "Güvenli sohbet - Bağlantılar sansürlenir",
|
||||||
|
"joinChat": "Sohbete Katıl",
|
||||||
|
"chooseUsername": "Oyuncu Sohbetine katılmak için bir kullanıcı adı seçin",
|
||||||
|
"username": "Kullanıcı Adı",
|
||||||
|
"usernamePlaceholder": "Kullanıcı adınızı girin...",
|
||||||
|
"usernameHint": "3-20 karakter, yalnızca harfler, sayılar, - ve _",
|
||||||
|
"joinButton": "Sohbete Katıl",
|
||||||
|
"colorModal": {
|
||||||
|
"title": "Kullanıcı Adı Rengini Özelleştir",
|
||||||
|
"chooseSolid": "Düz bir renk seçin:",
|
||||||
|
"customColor": "Özel renk:",
|
||||||
|
"preview": "Ön izleme:",
|
||||||
|
"previewUsername": "Kullanıcı Adı",
|
||||||
|
"apply": "Rengi Uygula"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "AYARLAR",
|
||||||
|
"java": "Java Çalışma Zamanı",
|
||||||
|
"useCustomJava": "Özel Java Yolunu Kullan",
|
||||||
|
"javaDescription": "Yüklü Java çalışma zamanını kendi kurulumunuzla geçersiz kılın",
|
||||||
|
"javaPath": "Java Çalıştırılabilir Yolu",
|
||||||
|
"javaPathPlaceholder": "Java yolunu seçin...",
|
||||||
|
"javaBrowse": "Gözat",
|
||||||
|
"javaHint": "Java kurulum klasörünü seçin (Windows, Mac, Linux destekler)",
|
||||||
|
"discord": "Discord Entegrasyonu",
|
||||||
|
"enableRPC": "Discord Rich Presence'ı Etkinleştir",
|
||||||
|
"discordDescription": "Başlatıcı etkinliğinizi Discord'da gösterin",
|
||||||
|
"game": "Oyun Seçenekleri",
|
||||||
|
"playerName": "Oyuncu Adı",
|
||||||
|
"playerNamePlaceholder": "Oyuncu adınızı girin",
|
||||||
|
"playerNameHint": "Bu ad oyun içinde kullanılacak (1-16 karakter)",
|
||||||
|
"openGameLocation": "Oyun Konumunu Aç",
|
||||||
|
"openGameLocationDesc": "Oyun kurulum klasörünü açın",
|
||||||
|
"account": "Oyuncu UUID Yönetimi",
|
||||||
|
"currentUUID": "Geçerli UUID",
|
||||||
|
"uuidPlaceholder": "UUID yükleniyor...",
|
||||||
|
"copyUUID": "UUID'yi Kopyala",
|
||||||
|
"regenerateUUID": "UUID'yi Yeniden Oluştur",
|
||||||
|
"uuidHint": "Bu kullanıcı adı için benzersiz oyuncu tanımlayıcınız",
|
||||||
|
"manageUUIDs": "Tüm UUID'leri Yönet",
|
||||||
|
"manageUUIDsDesc": "Tüm oyuncu UUID'lerini görüntüleyin ve yönetin",
|
||||||
|
"language": "Dil",
|
||||||
|
"selectLanguage": "Dil Seçin",
|
||||||
|
"repairGame": "Oyunu Onarı",
|
||||||
|
"reinstallGame": "Oyun dosyalarını yeniden kur (veri korur)",
|
||||||
|
"gpuPreference": "GPU Tercihi",
|
||||||
|
"gpuHint": "Tercih ettiğiniz GPU'yu seçin (Linux: DRI_PRIME'ı etkiler)",
|
||||||
|
"gpuAuto": "Otomatik",
|
||||||
|
"gpuIntegrated": "Entegre",
|
||||||
|
"gpuDedicated": "Ayrılmış",
|
||||||
|
"logs": "SİSTEM KAYITLARI",
|
||||||
|
"logsCopy": "Kopyala",
|
||||||
|
"logsRefresh": "Yenile",
|
||||||
|
"logsFolder": "Klasörü Aç",
|
||||||
|
"logsLoading": "Loglar yükleniyor...",
|
||||||
|
"closeLauncher": "Başlatıcı Davranışı",
|
||||||
|
"closeOnStart": "Oyun başlatıldığında Başlatıcıyı Kapat",
|
||||||
|
"closeOnStartDescription": "Hytale başlatıldıktan sonra başlatıcıyı otomatik olarak kapatın",
|
||||||
|
"gameBranch": "Oyun Dalı",
|
||||||
|
"branchRelease": "Yayın",
|
||||||
|
"branchPreRelease": "Ön-Yayın",
|
||||||
|
"branchHint": "Stabil yayın ve deneysel ön-yayın sürümleri arasında geçiş yapın",
|
||||||
|
"branchWarning": "Dalı değiştirmek farklı bir oyun sürümünü indirecek ve kuracaktır",
|
||||||
|
"branchSwitching": "{branch} sürümüne geçiliyor...",
|
||||||
|
"branchSwitched": "{branch} sürümüne başarıyla geçildi!",
|
||||||
|
"installRequired": "Kurulum Gerekli",
|
||||||
|
"branchInstallConfirm": "Oyun {branch} dalı için kurulacak. Devam et?"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"modalTitle": "UUID Yönetimi",
|
||||||
|
"currentUserUUID": "Geçerli Kullanıcı UUID",
|
||||||
|
"allPlayerUUIDs": "Tüm Oyuncu UUID'leri",
|
||||||
|
"generateNew": "Yeni UUID Oluştur",
|
||||||
|
"loadingUUIDs": "UUID'ler yükleniyor...",
|
||||||
|
"setCustomUUID": "Özel UUID Ayarla",
|
||||||
|
"customPlaceholder": "Özel UUID girin (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
|
||||||
|
"setUUID": "UUID Ayarla",
|
||||||
|
"warning": "Uyarı: Özel bir UUID ayarlamak geçerli oyuncu kimliğinizi değiştirecektir",
|
||||||
|
"copyTooltip": "UUID'yi Kopyala",
|
||||||
|
"regenerateTooltip": "Yeni UUID Oluştur"
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"modalTitle": "Profilleri Yönet",
|
||||||
|
"newProfilePlaceholder": "Yeni Profil Adı",
|
||||||
|
"createProfile": "Profil Oluştur"
|
||||||
|
},
|
||||||
|
"discord": {
|
||||||
|
"notificationText": "Discord topluluğumuza katılın!",
|
||||||
|
"joinButton": "Discord'a Katıl"
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"confirm": "Onayla",
|
||||||
|
"cancel": "İptal",
|
||||||
|
"save": "Kaydet",
|
||||||
|
"close": "Kapat",
|
||||||
|
"delete": "Sil",
|
||||||
|
"edit": "Düzenle",
|
||||||
|
"loading": "Yükleniyor...",
|
||||||
|
"apply": "Uygula",
|
||||||
|
"install": "Kur"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"gameDataNotFound": "Hata: Oyun verileri bulunamadı",
|
||||||
|
"gameUpdatedSuccess": "Oyun başarıyla güncellendi! 🎉",
|
||||||
|
"updateFailed": "Güncelleme başarısız: {error}",
|
||||||
|
"updateError": "Güncelleme hatası: {error}",
|
||||||
|
"discordEnabled": "Discord Rich Presence etkinleştirildi",
|
||||||
|
"discordDisabled": "Discord Rich Presence devre dışı bırakıldı",
|
||||||
|
"discordSaveFailed": "Discord ayarı kaydedilemedi",
|
||||||
|
"playerNameRequired": "Lütfen geçerli bir oyuncu adı girin",
|
||||||
|
"playerNameSaved": "Oyuncu adı başarıyla kaydedildi",
|
||||||
|
"playerNameSaveFailed": "Oyuncu adı kaydedilemedi",
|
||||||
|
"uuidCopied": "UUID panoya kopyalandı!",
|
||||||
|
"uuidCopyFailed": "UUID kopyalanamadı",
|
||||||
|
"uuidRegenNotAvailable": "UUID yeniden oluşturma kullanılamıyor",
|
||||||
|
"uuidRegenFailed": "UUID yeniden oluşturulamadı",
|
||||||
|
"uuidGenerated": "Yeni UUID başarıyla oluşturuldu!",
|
||||||
|
"uuidGeneratedShort": "Yeni UUID oluşturuldu!",
|
||||||
|
"uuidGenerateFailed": "Yeni UUID oluşturulamadı",
|
||||||
|
"uuidRequired": "Lütfen bir UUID girin",
|
||||||
|
"uuidInvalidFormat": "Geçersiz UUID formatı",
|
||||||
|
"uuidSetFailed": "Özel UUID ayarlanamadı",
|
||||||
|
"uuidSetSuccess": "Özel UUID başarıyla ayarlandı!",
|
||||||
|
"uuidDeleteFailed": "UUID silinemedi",
|
||||||
|
"uuidDeleteSuccess": "UUID başarıyla silindi!",
|
||||||
|
"modsDownloading": "{name} indiriliyor...",
|
||||||
|
"modsTogglingMod": "Mod değiştiriliyor...",
|
||||||
|
"modsDeletingMod": "Mod siliniyor...",
|
||||||
|
"modsLoadingMods": "CurseForge'dan modlar yükleniyor...",
|
||||||
|
"modsInstalledSuccess": "{name} başarıyla kuruldu! 🎉",
|
||||||
|
"modsDeletedSuccess": "{name} başarıyla silindi",
|
||||||
|
"modsDownloadFailed": "Mod indirilemedi: {error}",
|
||||||
|
"modsToggleFailed": "Mod değiştirilemedi: {error}",
|
||||||
|
"modsDeleteFailed": "Mod silinemedi: {error}",
|
||||||
|
"modsModNotFound": "Mod bilgileri bulunamadı"
|
||||||
|
},
|
||||||
|
"confirm": {
|
||||||
|
"defaultTitle": "Eylemi onayla",
|
||||||
|
"regenerateUuidTitle": "Yeni UUID oluştur",
|
||||||
|
"regenerateUuidMessage": "Yeni bir UUID oluşturmak istediğinizden emin misiniz? Bu oyuncu kimliğinizi değiştirecektir.",
|
||||||
|
"regenerateUuidButton": "Oluştur",
|
||||||
|
"setCustomUuidTitle": "Özel UUID ayarla",
|
||||||
|
"setCustomUuidMessage": "Bu özel UUID'yi ayarlamak istediğinizden emin misiniz? Bu oyuncu kimliğinizi değiştirecektir.",
|
||||||
|
"setCustomUuidButton": "UUID Ayarla",
|
||||||
|
"deleteUuidTitle": "UUID'yi sil",
|
||||||
|
"deleteUuidMessage": "\"{username}\" için UUID'yi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||||
|
"deleteUuidButton": "Sil",
|
||||||
|
"uninstallGameTitle": "Oyunu kaldır",
|
||||||
|
"uninstallGameMessage": "Hytale'yi kaldırmak istediğinizden emin misiniz? Tüm oyun dosyaları silinecektir.",
|
||||||
|
"uninstallGameButton": "Kaldır"
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"initializing": "Başlatılıyor...",
|
||||||
|
"downloading": "İndiriliyor...",
|
||||||
|
"installing": "Kuruluyur...",
|
||||||
|
"extracting": "Ayıklanıyor...",
|
||||||
|
"verifying": "Doğrulanıyor...",
|
||||||
|
"switchingProfile": "Profil değiştiriliyor...",
|
||||||
|
"profileSwitched": "Profil değiştirildi!",
|
||||||
|
"startingGame": "Oyun başlatılıyor...",
|
||||||
|
"launching": "BAŞLATILIYOR...",
|
||||||
|
"uninstallingGame": "Oyun kaldırılıyor...",
|
||||||
|
"gameUninstalled": "Oyun başarıyla kaldırıldı!",
|
||||||
|
"uninstallFailed": "Kaldırma başarısız: {error}",
|
||||||
|
"startingUpdate": "Zorunlu oyun güncellemesi başlatılıyor...",
|
||||||
|
"installationComplete": "Kurulum başarıyla tamamlandı!",
|
||||||
|
"installationFailed": "Kurulum başarısız: {error}",
|
||||||
|
"installingGameFiles": "Oyun dosyaları kuruluyor...",
|
||||||
|
"installComplete": "Kurulum tamamlandı!"
|
||||||
|
}
|
||||||
|
}
|
||||||
169
GUI/style.css
169
GUI/style.css
@@ -1815,6 +1815,12 @@ body {
|
|||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-retry-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.progress-retry-info {
|
.progress-retry-info {
|
||||||
color: #fbbf24;
|
color: #fbbf24;
|
||||||
font-family: 'JetBrains Mono', monospace;
|
font-family: 'JetBrains Mono', monospace;
|
||||||
@@ -5985,3 +5991,166 @@ select.settings-input option {
|
|||||||
opacity: 1;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,12 +15,6 @@ class AppUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupAutoUpdater() {
|
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
|
// Configure logger for electron-updater
|
||||||
// Create a compatible logger interface
|
// Create a compatible logger interface
|
||||||
@@ -176,7 +170,7 @@ class AppUpdater {
|
|||||||
console.warn('macOS update error: App may not be code-signed. Auto-update requires code signing.');
|
console.warn('macOS update error: App may not be code-signed. Auto-update requires code signing.');
|
||||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||||
this.mainWindow.webContents.send('update-error', {
|
this.mainWindow.webContents.send('update-error', {
|
||||||
message: 'Auto-update requires code signing. Please download manually from GitHub.',
|
message: 'Please download manually from GitHub.',
|
||||||
code: err.code,
|
code: err.code,
|
||||||
isMacSigningError: true,
|
isMacSigningError: true,
|
||||||
requiresManualDownload: true,
|
requiresManualDownload: true,
|
||||||
|
|||||||
@@ -520,9 +520,17 @@ async function installGame(playerName = 'Player', progressCallback, javaPathOver
|
|||||||
try {
|
try {
|
||||||
await downloadJRE(progressCallback, customCacheDir, customJreDir);
|
await downloadJRE(progressCallback, customCacheDir, customJreDir);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// Don't immediately fall back to system Java for JRE download errors - let user retry
|
||||||
|
if (error.isJREError) {
|
||||||
|
console.error('[Install] JRE download failed, allowing user retry:', error.message);
|
||||||
|
throw error; // Re-throw JRE errors to trigger retry UI
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-download JRE errors, fall back to system Java
|
||||||
const fallback = await detectSystemJava();
|
const fallback = await detectSystemJava();
|
||||||
if (fallback) {
|
if (fallback) {
|
||||||
javaBin = fallback;
|
javaBin = fallback;
|
||||||
|
console.log('[Install] Using system Java as fallback');
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const tar = require('tar');
|
|||||||
const { expandHome, JRE_DIR } = require('../core/paths');
|
const { expandHome, JRE_DIR } = require('../core/paths');
|
||||||
const { getOS, getArch } = require('../utils/platformUtils');
|
const { getOS, getArch } = require('../utils/platformUtils');
|
||||||
const { loadConfig } = require('../core/config');
|
const { loadConfig } = require('../core/config');
|
||||||
const { downloadFile } = require('../utils/fileManager');
|
const { downloadFile, retryDownload } = require('../utils/fileManager');
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
const JAVA_EXECUTABLE = 'java' + (process.platform === 'win32' ? '.exe' : '');
|
const JAVA_EXECUTABLE = 'java' + (process.platform === 'win32' ? '.exe' : '');
|
||||||
@@ -188,6 +188,20 @@ async function getJavaDetection() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual retry function for JRE downloads
|
||||||
|
async function retryJREDownload(url, cacheFile, progressCallback) {
|
||||||
|
console.log('Initiating manual JRE retry...');
|
||||||
|
|
||||||
|
// Ensure cache directory exists before retrying
|
||||||
|
const cacheDir = path.dirname(cacheFile);
|
||||||
|
if (!fs.existsSync(cacheDir)) {
|
||||||
|
console.log('Creating JRE cache directory:', cacheDir);
|
||||||
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return await retryDownload(url, cacheFile, progressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) {
|
async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) {
|
||||||
if (!fs.existsSync(cacheDir)) {
|
if (!fs.existsSync(cacheDir)) {
|
||||||
fs.mkdirSync(cacheDir, { recursive: true });
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
@@ -230,7 +244,40 @@ async function downloadJRE(progressCallback, cacheDir, jreDir = JRE_DIR) {
|
|||||||
progressCallback('Fetching Java runtime...', null, null, null, null);
|
progressCallback('Fetching Java runtime...', null, null, null, null);
|
||||||
}
|
}
|
||||||
console.log('Fetching Java runtime...');
|
console.log('Fetching Java runtime...');
|
||||||
await downloadFile(platform.url, cacheFile, progressCallback);
|
let jreFile;
|
||||||
|
try {
|
||||||
|
jreFile = await downloadFile(platform.url, cacheFile, progressCallback);
|
||||||
|
|
||||||
|
// If downloadFile returns false or undefined, it means the download failed
|
||||||
|
// We should retry the download with a manual retry
|
||||||
|
if (!jreFile || typeof jreFile !== 'string') {
|
||||||
|
console.log('[JRE Download] JRE file download failed or incomplete, attempting retry...');
|
||||||
|
jreFile = await retryJREDownload(platform.url, cacheFile, progressCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double-check we have a valid file
|
||||||
|
if (!jreFile || typeof jreFile !== 'string') {
|
||||||
|
throw new Error(`JRE download failed: received invalid path ${jreFile}. Please retry download.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (downloadError) {
|
||||||
|
console.error('[JRE Download] JRE download failed:', downloadError.message);
|
||||||
|
|
||||||
|
// Enhance error with retry information for the UI
|
||||||
|
const enhancedError = new Error(`JRE download failed: ${downloadError.message}`);
|
||||||
|
enhancedError.originalError = downloadError;
|
||||||
|
enhancedError.canRetry = downloadError.isConnectionLost ? false : (downloadError.canRetry !== false);
|
||||||
|
enhancedError.jreUrl = platform.url;
|
||||||
|
enhancedError.jreDest = cacheFile;
|
||||||
|
enhancedError.osName = osName;
|
||||||
|
enhancedError.arch = arch;
|
||||||
|
enhancedError.fileName = fileName;
|
||||||
|
enhancedError.cacheDir = cacheDir;
|
||||||
|
enhancedError.isJREError = true; // Flag to identify JRE errors
|
||||||
|
enhancedError.isConnectionLost = downloadError.isConnectionLost || false;
|
||||||
|
|
||||||
|
throw enhancedError;
|
||||||
|
}
|
||||||
console.log('Download finished');
|
console.log('Download finished');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -423,7 +423,8 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
|||||||
const canRetry = (error.canRetry === false) ? false : isRetryable;
|
const canRetry = (error.canRetry === false) ? false : isRetryable;
|
||||||
|
|
||||||
if (!canRetry || attempt === maxRetries - 1) {
|
if (!canRetry || attempt === maxRetries - 1) {
|
||||||
retryState.canRetry = false;
|
// Don't set retryState.canRetry to false for max retries - user should still be able to retry manually
|
||||||
|
retryState.canRetry = error.canRetry === false ? false : true;
|
||||||
console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`);
|
console.error(`Non-retryable error or max retries reached: ${error.code || error.message}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -439,6 +440,9 @@ async function downloadFile(url, dest, progressCallback, maxRetries = 5) {
|
|||||||
enhancedError.retryState = retryState;
|
enhancedError.retryState = retryState;
|
||||||
enhancedError.lastError = lastError;
|
enhancedError.lastError = lastError;
|
||||||
enhancedError.detailedError = detailedError;
|
enhancedError.detailedError = detailedError;
|
||||||
|
|
||||||
|
// Allow manual retry unless it's a connection lost error
|
||||||
|
enhancedError.canRetry = !lastError?.isConnectionLost && lastError?.canRetry !== false;
|
||||||
throw enhancedError;
|
throw enhancedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,6 +547,13 @@ async function retryDownload(url, dest, progressCallback, previousError = null)
|
|||||||
additionalRetries = Math.max(2, 5 - previousError.retryState.attempts);
|
additionalRetries = Math.max(2, 5 - previousError.retryState.attempts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure cache directory exists before retrying
|
||||||
|
const destDir = path.dirname(dest);
|
||||||
|
if (!fs.existsSync(destDir)) {
|
||||||
|
console.log('Creating cache directory:', destDir);
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await downloadFile(url, dest, progressCallback, additionalRetries);
|
await downloadFile(url, dest, progressCallback, additionalRetries);
|
||||||
console.log('Manual retry successful');
|
console.log('Manual retry successful');
|
||||||
|
|||||||
192
main.js
192
main.js
@@ -1,8 +1,10 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('dotenv').config({ path: path.join(__dirname, '.env') });
|
require('dotenv').config({ path: path.join(__dirname, '.env') });
|
||||||
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
|
||||||
|
const { autoUpdater } = require('electron-updater');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
|
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveChatUsername, loadChatUsername, saveChatColor, loadChatColor, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched } = require('./backend/launcher');
|
||||||
|
const { retryPWRDownload } = require('./backend/managers/gameManager');
|
||||||
|
|
||||||
const logger = require('./backend/logger');
|
const logger = require('./backend/logger');
|
||||||
const profileManager = require('./backend/managers/profileManager');
|
const profileManager = require('./backend/managers/profileManager');
|
||||||
@@ -160,7 +162,60 @@ function createWindow() {
|
|||||||
// Initialize Discord Rich Presence
|
// Initialize Discord Rich Presence
|
||||||
initDiscordRPC();
|
initDiscordRPC();
|
||||||
|
|
||||||
// Auto-updates handled by electron-updater
|
// Configure and initialize electron-updater
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
|
||||||
|
autoUpdater.on('checking-for-update', () => {
|
||||||
|
console.log('Checking for launcher updates...');
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-available', (info) => {
|
||||||
|
console.log('Update available:', info.version);
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('update-available', {
|
||||||
|
currentVersion: app.getVersion(),
|
||||||
|
newVersion: info.version,
|
||||||
|
releaseNotes: info.releaseNotes,
|
||||||
|
releaseDate: info.releaseDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-not-available', (info) => {
|
||||||
|
console.log('Launcher is up to date:', info.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('error', (err) => {
|
||||||
|
console.error('Error in auto-updater:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('download-progress', (progressObj) => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('update-download-progress', {
|
||||||
|
percent: progressObj.percent,
|
||||||
|
transferred: progressObj.transferred,
|
||||||
|
total: progressObj.total,
|
||||||
|
bytesPerSecond: progressObj.bytesPerSecond
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', (info) => {
|
||||||
|
console.log('Update downloaded:', info.version);
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('update-downloaded', {
|
||||||
|
version: info.version
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for updates after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
autoUpdater.checkForUpdates().catch(err => {
|
||||||
|
console.log('Failed to check for updates:', err.message);
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
mainWindow.webContents.on('devtools-opened', () => {
|
mainWindow.webContents.on('devtools-opened', () => {
|
||||||
mainWindow.webContents.closeDevTools();
|
mainWindow.webContents.closeDevTools();
|
||||||
@@ -430,37 +485,41 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
|
|||||||
console.log('[Main] Returning success response for install-game:', successResponse);
|
console.log('[Main] Returning success response for install-game:', successResponse);
|
||||||
return successResponse;
|
return successResponse;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Install error:', error);
|
// console.error('Install error:', error);
|
||||||
const errorMessage = error.message || error.toString();
|
const errorMessage = error.message || error.toString();
|
||||||
|
|
||||||
// Enhanced error data extraction for both download and Butler errors
|
// Enhanced error data extraction for both download and Butler errors
|
||||||
let errorData = {
|
let errorData = {
|
||||||
message: errorMessage,
|
message: errorMessage,
|
||||||
error: true,
|
error: true,
|
||||||
canRetry: true,
|
canRetry: true, // Default to true, will be overridden by specific error props
|
||||||
retryData: null
|
retryData: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Prioritize JRE errors first
|
||||||
|
if (error.isJREError) {
|
||||||
|
console.log('[Main] Processing JRE download error with retry context');
|
||||||
|
errorData.retryData = {
|
||||||
|
isJREError: true,
|
||||||
|
jreUrl: error.jreUrl,
|
||||||
|
fileName: error.fileName,
|
||||||
|
cacheDir: error.cacheDir,
|
||||||
|
osName: error.osName,
|
||||||
|
arch: error.arch
|
||||||
|
};
|
||||||
|
// For JRE errors, allow manual retry unless explicitly disabled
|
||||||
|
errorData.canRetry = error.canRetry !== false;
|
||||||
|
errorData.errorType = 'jre';
|
||||||
|
}
|
||||||
// Handle Butler-specific errors
|
// Handle Butler-specific errors
|
||||||
if (error.butlerError) {
|
else if (error.butlerError) {
|
||||||
console.log('[Main] Processing Butler error with retry context');
|
console.log('[Main] Processing Butler error with retry context');
|
||||||
errorData.retryData = {
|
errorData.retryData = {
|
||||||
branch: error.branch || 'release',
|
branch: error.branch || 'release',
|
||||||
fileName: error.fileName || '4.pwr',
|
fileName: error.fileName || '4.pwr',
|
||||||
cacheDir: error.cacheDir
|
cacheDir: error.cacheDir
|
||||||
};
|
};
|
||||||
errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true;
|
errorData.canRetry = error.canRetry !== false;
|
||||||
|
|
||||||
// Add Butler-specific error details
|
|
||||||
if (error.stderr) {
|
|
||||||
console.error('[Main] Butler stderr:', error.stderr);
|
|
||||||
}
|
|
||||||
if (error.stdout) {
|
|
||||||
console.log('[Main] Butler stdout:', error.stdout);
|
|
||||||
}
|
|
||||||
if (error.errorCode) {
|
|
||||||
console.log('[Main] Butler error code:', error.errorCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Handle PWR download errors
|
// Handle PWR download errors
|
||||||
else if (error.branch && error.fileName) {
|
else if (error.branch && error.fileName) {
|
||||||
@@ -470,7 +529,7 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
|
|||||||
fileName: error.fileName,
|
fileName: error.fileName,
|
||||||
cacheDir: error.cacheDir
|
cacheDir: error.cacheDir
|
||||||
};
|
};
|
||||||
errorData.canRetry = error.canRetry !== undefined ? error.canRetry : true;
|
errorData.canRetry = error.canRetry !== false;
|
||||||
}
|
}
|
||||||
// Default fallback for other errors
|
// Default fallback for other errors
|
||||||
else {
|
else {
|
||||||
@@ -479,6 +538,8 @@ ipcMain.handle('install-game', async (event, playerName, javaPath, installPath,
|
|||||||
branch: 'release',
|
branch: 'release',
|
||||||
fileName: '4.pwr'
|
fileName: '4.pwr'
|
||||||
};
|
};
|
||||||
|
// For generic errors, assume it's retryable unless specified
|
||||||
|
errorData.canRetry = error.canRetry !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send enhanced error info for retry UI
|
// Send enhanced error info for retry UI
|
||||||
@@ -636,7 +697,7 @@ ipcMain.handle('uninstall-game', async () => {
|
|||||||
try {
|
try {
|
||||||
await uninstallGame();
|
await uninstallGame();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Uninstall error:', error);
|
// console.error('Uninstall error:', error);
|
||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -670,15 +731,6 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
|||||||
try {
|
try {
|
||||||
console.log('[IPC] retry-download called with data:', retryData);
|
console.log('[IPC] retry-download called with data:', retryData);
|
||||||
|
|
||||||
// Handle null retry data gracefully
|
|
||||||
if (!retryData || !retryData.branch || !retryData.fileName) {
|
|
||||||
console.log('[IPC] Invalid retry data, using defaults');
|
|
||||||
retryData = {
|
|
||||||
branch: 'release',
|
|
||||||
fileName: '4.pwr'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressCallback = (message, percent, speed, downloaded, total, retryState) => {
|
const progressCallback = (message, percent, speed, downloaded, total, retryState) => {
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -693,14 +745,36 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle JRE download retries
|
||||||
|
if (retryData && retryData.isJREError) {
|
||||||
|
console.log(`[IPC] Retrying JRE download: jreUrl=${retryData.jreUrl}, fileName=${retryData.fileName}`);
|
||||||
|
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);
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle PWR download retries (default)
|
||||||
|
if (!retryData || !retryData.branch || !retryData.fileName) {
|
||||||
|
console.log('[IPC] Invalid retry data, using PWR defaults');
|
||||||
|
retryData = {
|
||||||
|
branch: 'release',
|
||||||
|
fileName: '4.pwr'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Extract PWR download info from retryData
|
// Extract PWR download info from retryData
|
||||||
const branch = retryData.branch;
|
const branch = retryData.branch;
|
||||||
const fileName = retryData.fileName;
|
const fileName = retryData.fileName;
|
||||||
const cacheDir = retryData.cacheDir;
|
const cacheDir = retryData.cacheDir;
|
||||||
|
|
||||||
console.log(`[IPC] Retrying PWR download: branch=${branch}, fileName=${fileName}`);
|
console.log(`[IPC] Retrying PWR download: branch=${branch}, fileName=${fileName}`);
|
||||||
|
console.log('[IPC] Full PWR retry data:', JSON.stringify(retryData, null, 2));
|
||||||
|
|
||||||
// Perform the retry with enhanced context
|
// Perform retry with enhanced context
|
||||||
await retryPWRDownload(branch, fileName, progressCallback, cacheDir);
|
await retryPWRDownload(branch, fileName, progressCallback, cacheDir);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
@@ -710,15 +784,28 @@ ipcMain.handle('retry-download', async (event, retryData) => {
|
|||||||
|
|
||||||
// Send error update to frontend with context
|
// Send error update to frontend with context
|
||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
const data = {
|
const isJreError = retryData?.isJREError;
|
||||||
message: errorMessage,
|
const errorRetryData = isJreError ?
|
||||||
error: true,
|
{
|
||||||
canRetry: true,
|
isJREError: true,
|
||||||
retryData: {
|
jreUrl: retryData?.jreUrl,
|
||||||
|
fileName: retryData?.fileName,
|
||||||
|
cacheDir: retryData?.cacheDir,
|
||||||
|
osName: retryData?.osName,
|
||||||
|
arch: retryData?.arch
|
||||||
|
} :
|
||||||
|
{
|
||||||
branch: retryData?.branch || 'release',
|
branch: retryData?.branch || 'release',
|
||||||
fileName: retryData?.fileName || '4.pwr',
|
fileName: retryData?.fileName || '4.pwr',
|
||||||
cacheDir: retryData?.cacheDir
|
cacheDir: retryData?.cacheDir
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
message: errorMessage,
|
||||||
|
error: true,
|
||||||
|
canRetry: error.canRetry !== false, // Respect canRetry from the thrown error
|
||||||
|
retryData: errorRetryData,
|
||||||
|
errorType: isJreError ? 'jre' : 'general' // Add errorType for the UI
|
||||||
};
|
};
|
||||||
mainWindow.webContents.send('progress-update', data);
|
mainWindow.webContents.send('progress-update', data);
|
||||||
}
|
}
|
||||||
@@ -846,7 +933,6 @@ ipcMain.handle('load-settings', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
|
||||||
const { retryPWRDownload } = require('./backend/managers/gameManager');
|
|
||||||
const os = require('os');
|
const os = require('os');
|
||||||
|
|
||||||
ipcMain.handle('get-local-app-data', async () => {
|
ipcMain.handle('get-local-app-data', async () => {
|
||||||
@@ -953,14 +1039,38 @@ ipcMain.handle('copy-mod-file', async (event, sourcePath, modsPath) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-updates handled by electron-updater
|
// Electron-updater IPC handlers
|
||||||
// ipcMain.handle('check-for-updates', ...) - removed
|
ipcMain.handle('check-for-updates', async () => {
|
||||||
|
try {
|
||||||
|
const result = await autoUpdater.checkForUpdates();
|
||||||
|
return {
|
||||||
|
updateAvailable: result && result.updateInfo,
|
||||||
|
currentVersion: app.getVersion(),
|
||||||
|
updateInfo: result ? result.updateInfo : null
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking for updates:', error);
|
||||||
|
return { updateAvailable: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Auto-updates handled by electron-updater
|
ipcMain.handle('download-update', async () => {
|
||||||
// ipcMain.handle('open-download-page', ...) - removed
|
try {
|
||||||
|
await autoUpdater.downloadUpdate();
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading update:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Auto-updates handled by electron-updater
|
ipcMain.handle('install-update', () => {
|
||||||
// ipcMain.handle('get-update-info', ...) - removed
|
autoUpdater.quitAndInstall(false, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-launcher-version', () => {
|
||||||
|
return app.getVersion();
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-gpu-info', () => {
|
ipcMain.handle('get-gpu-info', () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
156
package-lock.json
generated
156
package-lock.json
generated
@@ -14,6 +14,7 @@
|
|||||||
"discord-rpc": "^4.0.1",
|
"discord-rpc": "^4.0.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"electron-updater": "^6.7.3",
|
"electron-updater": "^6.7.3",
|
||||||
|
"fs-extra": "^11.3.3",
|
||||||
"tar": "^6.2.1",
|
"tar": "^6.2.1",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
},
|
},
|
||||||
@@ -147,6 +148,21 @@
|
|||||||
"global-agent": "^3.0.0"
|
"global-agent": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@electron/get/node_modules/fs-extra": {
|
||||||
|
"version": "8.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
|
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.0",
|
||||||
|
"jsonfile": "^4.0.0",
|
||||||
|
"universalify": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6 <7 || >=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@electron/notarize": {
|
"node_modules/@electron/notarize": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz",
|
||||||
@@ -345,34 +361,6 @@
|
|||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/universal/node_modules/fs-extra": {
|
|
||||||
"version": "11.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
|
||||||
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^6.0.1",
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/universal/node_modules/jsonfile": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/universal/node_modules/minimatch": {
|
"node_modules/@electron/universal/node_modules/minimatch": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
@@ -389,16 +377,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/universal/node_modules/universalify": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/windows-sign": {
|
"node_modules/@electron/windows-sign": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
|
||||||
@@ -406,7 +384,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -421,50 +398,6 @@
|
|||||||
"node": ">=14.14"
|
"node": ">=14.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
|
|
||||||
"version": "11.3.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
|
||||||
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^6.0.1",
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/windows-sign/node_modules/jsonfile": {
|
|
||||||
"version": "6.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
|
||||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"universalify": "^2.0.0"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/windows-sign/node_modules/universalify": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 10.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@isaacs/balanced-match": {
|
"node_modules/@isaacs/balanced-match": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
@@ -916,6 +849,7 @@
|
|||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -1679,8 +1613,7 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
@@ -1897,6 +1830,7 @@
|
|||||||
"integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==",
|
"integrity": "sha512-ce4Ogns4VMeisIuCSK0C62umG0lFy012jd8LMZ6w/veHUeX4fqfDrGe+HTWALAEwK6JwKP+dhPvizhArSOsFbg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.4.0",
|
"app-builder-lib": "26.4.0",
|
||||||
"builder-util": "26.3.4",
|
"builder-util": "26.3.4",
|
||||||
@@ -2272,7 +2206,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -2293,7 +2226,6 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -2310,16 +2242,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/encoding": {
|
|
||||||
"version": "0.1.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"dependencies": {
|
|
||||||
"iconv-lite": "^0.6.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
@@ -2612,18 +2534,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-extra": {
|
"node_modules/fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "11.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^6.0.1",
|
||||||
"universalify": "^0.1.0"
|
"universalify": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6 <7 || >=8"
|
"node": ">=14.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fs-extra/node_modules/jsonfile": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"universalify": "^2.0.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"graceful-fs": "^4.1.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fs-extra/node_modules/universalify": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fs-minipass": {
|
"node_modules/fs-minipass": {
|
||||||
@@ -3561,7 +3503,6 @@
|
|||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -3914,6 +3855,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3943,7 +3885,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -3961,7 +3902,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -4158,7 +4098,6 @@
|
|||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
},
|
},
|
||||||
@@ -4619,7 +4558,6 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
|
|||||||
15
preload.js
15
preload.js
@@ -115,5 +115,20 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
activate: (id) => ipcRenderer.invoke('profile-activate', id),
|
activate: (id) => ipcRenderer.invoke('profile-activate', id),
|
||||||
delete: (id) => ipcRenderer.invoke('profile-delete', id),
|
delete: (id) => ipcRenderer.invoke('profile-delete', id),
|
||||||
update: (id, updates) => ipcRenderer.invoke('profile-update', id, updates)
|
update: (id, updates) => ipcRenderer.invoke('profile-update', id, updates)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Launcher Update API
|
||||||
|
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
||||||
|
downloadUpdate: () => ipcRenderer.invoke('download-update'),
|
||||||
|
installUpdate: () => ipcRenderer.invoke('install-update'),
|
||||||
|
getLauncherVersion: () => ipcRenderer.invoke('get-launcher-version'),
|
||||||
|
onUpdateAvailable: (callback) => {
|
||||||
|
ipcRenderer.on('update-available', (event, data) => callback(data));
|
||||||
|
},
|
||||||
|
onUpdateDownloadProgress: (callback) => {
|
||||||
|
ipcRenderer.on('update-download-progress', (event, data) => callback(data));
|
||||||
|
},
|
||||||
|
onUpdateDownloaded: (callback) => {
|
||||||
|
ipcRenderer.on('update-downloaded', (event, data) => callback(data));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user