Files
GDL-Continuation/src/common/modals/JavaSetup.js
T
2025-07-31 00:53:24 +02:00

630 lines
18 KiB
JavaScript

/* eslint-disable no-loop-func */
import React, { useState, useEffect, memo } from 'react';
import { Button, Progress, Input } from 'antd';
import { Transition } from 'react-transition-group';
import styled, { useTheme } from 'styled-components';
import { ipcRenderer } from 'electron';
import fse from 'fs-extra';
import { useSelector, useDispatch } from 'react-redux';
import path from 'path';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFolder } from '@fortawesome/free-solid-svg-icons';
import { exec } from 'child_process';
import { promisify } from 'util';
import Modal from '../components/Modal';
import { downloadFile } from '../../app/desktop/utils/downloader';
import {
convertOSToJavaFormat,
convertArchToJavaFormat,
extractAll,
isLatestJavaDownloaded
} from '../../app/desktop/utils';
import { _getTempPath } from '../utils/selectors';
import { closeModal } from '../reducers/modals/actions';
import {
updateJavaLatestPath,
updateJavaPath
} from '../reducers/settings/actions';
import { UPDATE_MODAL } from '../reducers/modals/actionTypes';
import { LATEST_JAVA_VERSION } from '../utils/constants';
const JavaSetup = () => {
const [step, setStep] = useState(0);
const [choice, setChoice] = useState(null);
const [isJava8Downloaded, setIsJava8Downloaded] = useState(null);
const [isJavaLatestDownloaded, setIsJavaLatestDownloaded] = useState(null);
const [java8Log, setJava8Log] = useState(null);
const [javaLatestLog, setJavaLatestLog] = useState(null);
const javaManifest = useSelector(state => state.app.javaManifest);
const javaLatestManifest = useSelector(state => state.app.javaLatestManifest);
const userData = useSelector(state => state.userData);
const manifests = {
javaLatest: javaLatestManifest,
java: javaManifest
};
useEffect(() => {
isLatestJavaDownloaded(manifests, userData, true, 8)
.then(e => {
setIsJava8Downloaded(e?.isValid);
return setJava8Log(e?.log);
})
.catch(err => console.error(err));
isLatestJavaDownloaded(manifests, userData, true, LATEST_JAVA_VERSION)
.then(e => {
setIsJavaLatestDownloaded(e?.isValid);
return setJavaLatestLog(e?.log);
})
.catch(err => console.error(err));
}, []);
return (
<Modal
title="Java Setup"
css={`
height: 380px;
width: 600px;
display: flex;
flex-direction: row;
justify-content: center;
padding: 20px;
position: relative;
`}
header={false}
>
<Transition in={step === 0} timeout={200}>
{state => (
<FirstStep state={state}>
<div
css={`
font-size: 28px;
text-align: center;
margin-bottom: 20px;
`}
>
Java Setup
</div>
<div
css={`
margin-bottom: 20px;
font-size: 18px;
text-align: justify;
`}
>
For an optimal experience, we suggest letting us take care of java
for you. Only manually manage java if you know what you&apos;re
doing, it may result in GDLauncher not working!
</div>
<div
css={`
display: flex;
align-items: center;
justify-content: space-evenly;
margin-bottom: 40px;
opacity: 0;
opacity: ${isJava8Downloaded !== null &&
isJavaLatestDownloaded !== null &&
(!isJava8Downloaded || !isJavaLatestDownloaded) &&
'1'};
* > h3 {
border-radius: 5px;
padding: 2px 4px;
background: ${props => props.theme.palette.colors.red};
}
`}
>
<h3>Missing Versions:</h3>
<div
css={`
display: flex;
align-items: center;
margin-right: 40px;
h3 {
width: 71px;
display: flex;
justify-content: center;
align-content: center;
padding: 2px;
box-sizing: content-box;
}
`}
>
{!isJava8Downloaded && isJava8Downloaded !== null && (
<h3
css={`
margin-right: 20px;
`}
>
Java 8
</h3>
)}
{!isJavaLatestDownloaded && isJavaLatestDownloaded !== null && (
<h3>Java {LATEST_JAVA_VERSION}</h3>
)}
</div>
</div>
<div
css={`
& > div {
display: flex;
justify-content: center;
margin-top: 20px;
}
`}
>
<div>
<Button
type="primary"
css={`
width: 150px;
`}
onClick={() => {
setStep(1);
setChoice(0);
}}
>
Automatic Setup
</Button>
</div>
<div>
<Button
type="text"
css={`
width: 150px;
`}
onClick={() => {
setStep(1);
setChoice(1);
}}
>
Manual Setup
</Button>
</div>
</div>
</FirstStep>
)}
</Transition>
<Transition in={step === 1} timeout={200}>
{state => (
<SecondStep state={state}>
<div
css={`
font-size: 28px;
text-align: center;
margin-bottom: 20px;
`}
>
{choice === 0 ? 'Automatic' : 'Manual'} Setup
</div>
{choice === 0 ? (
<AutomaticSetup
isJava8Downloaded={isJava8Downloaded}
isJavaLatestDownloaded={isJavaLatestDownloaded}
java8Log={java8Log}
javaLatestLog={javaLatestLog}
/>
) : (
<ManualSetup setStep={setStep} />
)}
</SecondStep>
)}
</Transition>
</Modal>
);
};
const ManualSetup = ({ setStep }) => {
const [javaPath, setJavaPath] = useState('');
const [javaLatestPath, setJavaLatestPath] = useState('');
const dispatch = useDispatch();
const selectFolder = async version => {
const { filePaths, canceled } = await ipcRenderer.invoke('openFileDialog');
if (!canceled) {
if (version === LATEST_JAVA_VERSION) {
setJavaLatestPath(filePaths[0]);
} else setJavaPath(filePaths[0]);
}
};
return (
<div
css={`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
`}
>
<div
css={`
margin-bottom: 50px;
font-size: 18px;
`}
>
Enter the required paths to java. Java 8 will be used for all the
versions {'<'} 1.17, java {LATEST_JAVA_VERSION} for versions {'>='}{' '}
1.17. You can also use the same executable but some versions might not
run.
</div>
<div
css={`
width: 100%;
display: flex;
margin-bottom: 10px;
`}
>
<Input
placeholder="Select your Java8 executable (MC < 1.17)"
onChange={e => setJavaPath(e.target.value)}
value={javaPath}
/>
<Button
type="primary"
onClick={() => selectFolder(8)}
css={`
margin-left: 10px;
`}
>
<FontAwesomeIcon icon={faFolder} />
</Button>
</div>
<div
css={`
width: 100%;
display: flex;
`}
>
<Input
placeholder={`Select your Java ${LATEST_JAVA_VERSION} executable (MC >= 1.17)`}
onChange={e => setJavaLatestPath(e.target.value)}
value={javaLatestPath}
/>
<Button
type="primary"
onClick={() => selectFolder(LATEST_JAVA_VERSION)}
css={`
margin-left: 10px;
`}
>
<FontAwesomeIcon icon={faFolder} />
</Button>
</div>
<div
css={`
width: 100%;
display: flex;
justify-content: space-between;
margin-top: 45px;
position: absolute;
bottom: 0;
`}
>
<Button type="primary" onClick={() => setStep(0)}>
Go Back
</Button>
<Button
type="danger"
disabled={javaPath === '' || javaLatestPath === ''}
onClick={() => {
dispatch(updateJavaPath(javaPath));
dispatch(updateJavaLatestPath(javaLatestPath));
dispatch(closeModal());
}}
>
Continue with custom java
</Button>
</div>
</div>
);
};
const AutomaticSetup = ({
isJava8Downloaded,
isJavaLatestDownloaded,
java8Log,
javaLatestLog
}) => {
const [downloadPercentage, setDownloadPercentage] = useState(0);
const [currentSubStep, setCurrentSubStep] = useState('Downloading Java');
const [currentStepPercentage, setCurrentStepPercentage] = useState(0);
const javaManifest = useSelector(state => state.app.javaManifest);
const javaLatestManifest = useSelector(state => state.app.javaLatestManifest);
const userData = useSelector(state => state.userData);
const tempFolder = useSelector(_getTempPath);
const modals = useSelector(state => state.modals);
const dispatch = useDispatch();
const theme = useTheme();
const javaToInstall = [];
useEffect(() => {
if (javaToInstall.length > 0) {
const instanceManagerModalIndex = modals.findIndex(
x => x.modalType === 'JavaSetup'
);
dispatch({
type: UPDATE_MODAL,
modals: [
...modals.slice(0, instanceManagerModalIndex),
{
modalType: 'JavaSetup',
modalProps: { preventClose: true }
},
...modals.slice(instanceManagerModalIndex + 1)
]
});
}
}, []);
if (!isJava8Downloaded) javaToInstall.push(8);
if (!isJavaLatestDownloaded) javaToInstall.push(LATEST_JAVA_VERSION);
const installJava = async () => {
const javaOs = convertOSToJavaFormat(process.platform);
const javaArch = convertArchToJavaFormat(process.arch);
const java8Meta = javaManifest.find(
v =>
v.os === javaOs &&
v.architecture === javaArch &&
(v.binary_type === 'jre' || v.binary_type === 'jdk')
);
const javaLatestMeta = javaLatestManifest.find(
v =>
v.os === javaOs &&
v.architecture === javaArch &&
(v.binary_type === 'jre' || v.binary_type === 'jdk')
);
const totalExtractionSteps = process.platform !== 'win32' ? 2 : 1;
const totalSteps = (totalExtractionSteps + 1) * javaToInstall.length;
const setStepPercentage = (stepNumber, percentage) => {
setCurrentStepPercentage(
parseInt(percentage / totalSteps + (stepNumber * 100) / totalSteps, 10)
);
};
let index = 0;
for (const javaVersion of javaToInstall) {
const {
version_data: { openjdk_version: version },
binary_link: url
} = javaVersion === 8 ? java8Meta : javaLatestMeta;
const javaBaseFolder = path.join(userData, 'java');
await fse.remove(path.join(javaBaseFolder, version));
const downloadLocation = path.join(tempFolder, path.basename(url));
setCurrentSubStep(`Java ${javaVersion} - Downloading`);
await downloadFile(downloadLocation, url, p => {
ipcRenderer.invoke('update-progress-bar', p);
setDownloadPercentage(p);
setStepPercentage(index, p);
});
ipcRenderer.invoke('update-progress-bar', -1);
index += 1;
setDownloadPercentage(0);
setStepPercentage(index, 0);
await new Promise(resolve => setTimeout(resolve, 500));
setCurrentSubStep(
`Java ${javaVersion} - Extracting 1 / ${totalExtractionSteps}`
);
let { extractedParentDir } = await extractAll(
downloadLocation,
tempFolder,
{
$progress: true
},
{
update: percent => {
ipcRenderer.invoke('update-progress-bar', percent);
setDownloadPercentage(percent);
setStepPercentage(index, percent);
}
}
);
index += 1;
setDownloadPercentage(0);
setStepPercentage(index, 0);
await fse.remove(downloadLocation);
// If NOT windows then tar.gz instead of zip, so we need to extract 2 times.
if (process.platform !== 'win32') {
ipcRenderer.invoke('update-progress-bar', -1);
await new Promise(resolve => setTimeout(resolve, 500));
setCurrentSubStep(
`Java ${javaVersion} - Extracting 2 / ${totalExtractionSteps}`
);
const tempTarName = path.join(
tempFolder,
path.basename(url).replace('.tar.gz', '.tar')
);
({ extractedParentDir } = await extractAll(
tempTarName,
tempFolder,
{
$progress: true
},
{
update: percent => {
ipcRenderer.invoke('update-progress-bar', percent);
setDownloadPercentage(percent);
setStepPercentage(index, percent);
}
}
));
await fse.remove(tempTarName);
index += 1;
setDownloadPercentage(0);
setStepPercentage(index, 0);
}
const directoryToMove =
process.platform === 'darwin'
? path.join(tempFolder, extractedParentDir, 'Contents', 'Home')
: path.join(tempFolder, extractedParentDir);
await fse.move(directoryToMove, path.join(javaBaseFolder, version));
await fse.remove(path.join(tempFolder, extractedParentDir));
const ext = process.platform === 'win32' ? '.exe' : '';
if (process.platform !== 'win32') {
const execPath = path.join(
javaBaseFolder,
version,
'bin',
`java${ext}`
);
await promisify(exec)(`chmod +x "${execPath}"`);
await promisify(exec)(`chmod 755 "${execPath}"`);
}
}
dispatch(updateJavaPath(null));
dispatch(updateJavaLatestPath(null));
setCurrentSubStep(`Java is ready!`);
ipcRenderer.invoke('update-progress-bar', -1);
setDownloadPercentage(100);
setCurrentStepPercentage(100);
await new Promise(resolve => setTimeout(resolve, 2000));
if (!javaLatestLog || !java8Log) dispatch(closeModal());
};
useEffect(() => {
installJava();
}, []);
return (
<div
css={`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
`}
>
{javaToInstall.length > 0 ? (
<>
<div
css={`
margin-top: -15px; //cheaty way to get up to the Modal title :P
margin-bottom: 50px;
width: 50%;
`}
>
<Progress
percent={currentStepPercentage}
strokeColor={theme.palette.primary.main}
status="normal"
/>
</div>
<div
css={`
margin-bottom: 20px;
font-size: 18px;
`}
>
{currentSubStep}
</div>
<div
css={`
padding: 0 10px;
width: 100%;
`}
>
{downloadPercentage ? (
<Progress
percent={downloadPercentage}
strokeColor={theme.palette.primary.main}
status="normal"
/>
) : null}
</div>
</>
) : (
<div
css={`
display: flex;
flex-direction: column;
div {
display: flex;
flex-direction: column;
}
`}
>
<h2>Java is already installed!</h2>
<div
css={`
margin-bottom: 10px;
`}
>
<h3>Java 8 details:</h3>
<code>{java8Log}</code>
</div>
<div>
<h3>Java {LATEST_JAVA_VERSION} details:</h3>
<code>{javaLatestLog}</code>
</div>
</div>
)}
{javaLatestLog && java8Log && (
<Button
css={`
position: absolute;
bottom: 0;
right: 0;
`}
type="primary"
onClick={() => dispatch(closeModal())}
>
Close
</Button>
)}
</div>
);
};
export default memo(JavaSetup);
const FirstStep = styled.div`
transition: 0.2s ease-in-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
will-change: transform;
transform: translateX(
${({ state }) => (state === 'exiting' || state === 'exited' ? -100 : 0)}%
);
`;
const SecondStep = styled.div`
transition: 0.2s ease-in-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
will-change: transform;
transform: translateX(
${({ state }) => (state === 'entering' || state === 'entered' ? 0 : 101)}%
);
`;