Files
modrinth/apps/app-frontend/src/components/ui/JavaSelector.vue
T
2026-07-31 14:07:21 +01:00

310 lines
6.9 KiB
Vue

<template>
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
<div :id="props.id" class="toggle-setting" :class="{ compact }">
<div class="input-with-status">
<StyledInput
autocomplete="off"
:disabled="props.disabled"
:model-value="props.modelValue ? props.modelValue.path : ''"
:placeholder="placeholder ?? formatMessage(messages.pathPlaceholder)"
wrapper-class="installation-input"
@update:model-value="
(val) => {
emit('update:modelValue', {
...props.modelValue,
path: val,
})
}
"
/>
<Button
type="quiet"
:color="
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: undefined
"
:aria-label="formatMessage(messages.testJavaInstallation)"
class="!text-[var(--legacy-button-color,var(--color-base))] [&>svg]:!text-[var(--legacy-button-color,var(--color-primary))]"
:disabled="testingJava || props.disabled"
@click="runTest(props.modelValue?.path)"
@mouseenter="!props.disabled && (hoveringTest = true)"
@mouseleave="hoveringTest = false"
:style="{ '--legacy-button-color': (
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard'
) && (
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard'
) !== 'standard' ? `var(--color-${
!hoveringTest && !testingJava
? testingJavaSuccess === true
? 'green'
: 'red'
: 'standard'
})` : undefined }">
<SpinnerIcon v-if="testingJava" class="animate-spin h-4 w-4" />
<CheckCircleIcon
v-else-if="testingJavaSuccess === true && !hoveringTest"
class="h-4 w-4"
/>
<XCircleIcon v-else-if="testingJavaSuccess !== true && !hoveringTest" class="h-4 w-4" />
<RefreshCwIcon v-else-if="!props.disabled" class="h-4 w-4" />
</Button>
</div>
<span class="installation-buttons">
<Button v-if="props.version"
v-tooltip="
testingJavaSuccess === true ? formatMessage(messages.alreadyInstalled) : undefined
"
:disabled="props.disabled || installingJava || testingJavaSuccess === true"
@click="reinstallJava"
>
<DownloadIcon />
{{
installingJava
? formatMessage(messages.installing)
: formatMessage(messages.installRecommended)
}}
</Button>
<Button :disabled="props.disabled" @click="autoDetect">
<SearchIcon />
{{ formatMessage(messages.detect) }}
</Button>
<Button :disabled="props.disabled" @click="handleJavaFileInput()">
<FolderSearchIcon />
{{ formatMessage(messages.browse) }}
</Button>
</span>
</div>
</template>
<script setup>
import { Button } from '@modrinth/ui'
import {
CheckCircleIcon,
DownloadIcon,
FolderSearchIcon,
RefreshCwIcon,
SearchIcon,
SpinnerIcon,
XCircleIcon,
} from '@modrinth/assets'
import {
defineMessages,
injectNotificationManager,
StyledInput,
useVIntl,
} from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog'
import { ref, watch } from 'vue'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import useJavaTest from '@/composables/useJavaTest'
import { trackEvent } from '@/helpers/analytics'
import { auto_install_java, find_filtered_jres, get_jre } from '@/helpers/jre.js'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()
const messages = defineMessages({
pathPlaceholder: {
id: 'app.java-selector.path.placeholder',
defaultMessage: '/path/to/java',
},
testJavaInstallation: {
id: 'app.java-selector.test-installation',
defaultMessage: 'Test Java installation',
},
alreadyInstalled: {
id: 'app.java-selector.already-installed',
defaultMessage: 'Already installed',
},
installing: {
id: 'app.java-selector.installing',
defaultMessage: 'Installing...',
},
installRecommended: {
id: 'app.java-selector.install-recommended',
defaultMessage: 'Install recommended',
},
detect: {
id: 'app.java-selector.detect',
defaultMessage: 'Detect',
},
browse: {
id: 'app.java-selector.browse',
defaultMessage: 'Browse',
},
})
const props = defineProps({
id: {
type: String,
required: false,
default: null,
},
version: {
type: Number,
required: false,
default: null,
},
modelValue: {
type: Object,
default: () => ({
path: '',
version: '',
}),
},
disabled: {
type: Boolean,
required: false,
default: false,
},
placeholder: {
type: String,
required: false,
default: null,
},
compact: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue'])
const {
testingJava,
javaTestResult: testingJavaSuccess,
testJavaInstallationDebounced,
testJavaInstallation,
} = useJavaTest()
const installingJava = ref(false)
const hoveringTest = ref(false)
let hasInitialized = false
async function runTest(path) {
await testJavaInstallation(path, props.version, true)
}
watch(
() => props.modelValue?.path,
(newPath) => {
if (newPath) {
if (!hasInitialized) {
testJavaInstallation(newPath, props.version, false)
hasInitialized = true
} else {
testJavaInstallationDebounced(newPath, props.version)
}
}
},
{ immediate: true },
)
async function handleJavaFileInput() {
const filePath = await open()
if (filePath) {
let result = await get_jre(filePath.path ?? filePath).catch(handleError)
if (!result) {
result = {
path: filePath.path ?? filePath,
version: props.version.toString(),
parsed_version: props.version,
architecture: 'x86',
}
}
trackEvent('JavaManualSelect', {
version: props.version,
})
emit('update:modelValue', result)
}
}
const detectJavaModal = ref(null)
async function autoDetect() {
if (!props.compact) {
detectJavaModal.value.show(props.version, props.modelValue)
} else {
const versions = await find_filtered_jres(props.version).catch(handleError)
if (versions.length > 0) {
emit('update:modelValue', versions[0])
}
}
}
async function reinstallJava() {
installingJava.value = true
const path = await auto_install_java(props.version).catch(handleError)
let result = await get_jre(path)
if (!result) {
result = {
path: path,
version: props.version.toString(),
parsed_version: props.version,
architecture: 'x86',
}
}
trackEvent('JavaReInstall', {
path: path,
version: props.version,
})
emit('update:modelValue', result)
installingJava.value = false
runTest(result.path)
}
</script>
<style lang="scss" scoped>
.input-with-status {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
width: 100%;
min-width: 0;
}
.installation-input {
flex: 1 1 0;
min-width: 0;
}
.toggle-setting {
display: flex;
flex-wrap: wrap;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 0.5rem;
&.compact {
flex-wrap: wrap;
}
}
.installation-buttons {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
margin: 0;
}
</style>