You've already forked AstralRinth
Bugs again (#703)
* initial * more fixes * logs * more fixes * working rescuer * minor log display fix * mac fixes * minor fix * libsselinux1 * linux error * actions test * more bugs. Modpack page! BIG changes * changed minimum 64 -> 8 * removed modpack page moved to modal * removed unnecessary css * mac compile * many revs * Merge colorful logs (#725) * make implementation not dumb * run prettier * null -> true * Add line numbers & make errors more robust. * improvments * changes; virtual scroll --------- Co-authored-by: qtchaos <72168435+qtchaos@users.noreply.github.com> * omorphia colors, comments fix * fixes; _JAVA_OPTIONS * revs * mac specific * more mac * some fixes * quick fix * add java reinstall option --------- Co-authored-by: qtchaos <72168435+qtchaos@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
@@ -75,7 +75,7 @@
|
||||
</Card>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Promotion query-param="?r=launcher" />
|
||||
<Promotion :external="false" query-param="?r=launcher" />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
||||
@@ -84,6 +84,9 @@
|
||||
:instance="instance"
|
||||
:options="options"
|
||||
:offline="offline"
|
||||
:playing="playing"
|
||||
:versions="modrinthVersions"
|
||||
:installed="instance.install_stage !== 'installed'"
|
||||
></component>
|
||||
</Suspense>
|
||||
</template>
|
||||
@@ -149,6 +152,7 @@ import { isOffline, showProfileInFolder } from '@/helpers/utils.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useFetch } from '@/helpers/fetch'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -197,6 +201,15 @@ const checkProcess = async () => {
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
// Get information on associated modrinth versions, if any
|
||||
const modrinthVersions = ref([])
|
||||
if (!(await isOffline()) && instance.value.metadata.linked_data) {
|
||||
modrinthVersions.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${instance.value.metadata.linked_data.project_id}/version`,
|
||||
'project'
|
||||
)
|
||||
}
|
||||
|
||||
await checkProcess()
|
||||
|
||||
const stopInstance = async (context) => {
|
||||
|
||||
@@ -20,6 +20,15 @@
|
||||
Share
|
||||
</Button>
|
||||
<Button
|
||||
v-if="logs[selectedLogIndex] && logs[selectedLogIndex].live === true"
|
||||
@click="clearLiveLog()"
|
||||
>
|
||||
<TrashIcon />
|
||||
Clear
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
v-else
|
||||
:disabled="!logs[selectedLogIndex] || logs[selectedLogIndex].live === true"
|
||||
color="danger"
|
||||
@click="deleteLog()"
|
||||
@@ -29,14 +38,43 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="logContainer" class="log-text">
|
||||
<span
|
||||
v-for="(line, index) in logs[selectedLogIndex]?.stdout.split('\n')"
|
||||
:key="index"
|
||||
class="no-wrap"
|
||||
<div class="button-row">
|
||||
<input
|
||||
id="text-filter"
|
||||
v-model="searchFilter"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="text-filter"
|
||||
placeholder="Type to filter logs..."
|
||||
/>
|
||||
<div class="filter-group">
|
||||
<Checkbox
|
||||
v-for="level in levels"
|
||||
:key="level.toLowerCase()"
|
||||
v-model="levelFilters[level.toLowerCase()]"
|
||||
class="filter-checkbox"
|
||||
>
|
||||
{{ level }}</Checkbox
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-text">
|
||||
<RecycleScroller
|
||||
v-slot="{ item }"
|
||||
ref="logContainer"
|
||||
class="scroller"
|
||||
:items="displayProcessedLogs"
|
||||
direction="vertical"
|
||||
:item-size="20"
|
||||
key-field="id"
|
||||
>
|
||||
{{ line }} <br />
|
||||
</span>
|
||||
<div class="user no-wrap">
|
||||
<span :style="{ color: item.prefixColor, 'font-weight': item.weight }">{{
|
||||
item.prefix
|
||||
}}</span>
|
||||
<span :style="{ color: item.textColor }">{{ item.text }}</span>
|
||||
</div>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
<ShareModal
|
||||
ref="shareModal"
|
||||
@@ -56,20 +94,31 @@ import {
|
||||
ClipboardCopyIcon,
|
||||
DropdownSelect,
|
||||
ShareIcon,
|
||||
Checkbox,
|
||||
TrashIcon,
|
||||
ShareModal,
|
||||
} from 'omorphia'
|
||||
import { delete_logs_by_datetime, get_logs, get_output_by_datetime } from '@/helpers/logs.js'
|
||||
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import {
|
||||
delete_logs_by_filename,
|
||||
get_logs,
|
||||
get_output_by_filename,
|
||||
get_latest_log_cursor,
|
||||
} from '@/helpers/logs.js'
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import calendar from 'dayjs/plugin/calendar'
|
||||
import { get_output_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import isToday from 'dayjs/plugin/isToday'
|
||||
import isYesterday from 'dayjs/plugin/isYesterday'
|
||||
import { get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { process_listener } from '@/helpers/events.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { ofetch } from 'ofetch'
|
||||
|
||||
dayjs.extend(calendar)
|
||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
|
||||
dayjs.extend(isToday)
|
||||
dayjs.extend(isYesterday)
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -82,11 +131,21 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
playing: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const currentLiveLog = ref(null)
|
||||
const currentLiveLogCursor = ref(0)
|
||||
const emptyText = ['No live game detected.', 'Start your game to proceed']
|
||||
|
||||
const logs = ref([])
|
||||
await setLogs()
|
||||
|
||||
const logsColored = true
|
||||
|
||||
const selectedLogIndex = ref(0)
|
||||
const copied = ref(false)
|
||||
const logContainer = ref(null)
|
||||
@@ -95,16 +154,86 @@ const userScrolled = ref(false)
|
||||
const isAutoScrolling = ref(false)
|
||||
const shareModal = ref(null)
|
||||
|
||||
const levels = ['Comment', 'Error', 'Warn', 'Info', 'Debug', 'Trace']
|
||||
const levelFilters = ref({})
|
||||
levels.forEach((level) => {
|
||||
levelFilters.value[level.toLowerCase()] = true
|
||||
})
|
||||
const searchFilter = ref('')
|
||||
|
||||
function shouldDisplay(processedLine) {
|
||||
if (!processedLine.level) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!levelFilters.value[processedLine.level.toLowerCase()]) {
|
||||
return false
|
||||
}
|
||||
if (searchFilter.value !== '') {
|
||||
if (!processedLine.text.toLowerCase().includes(searchFilter.value.toLowerCase())) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Selects from the processed logs which ones should be displayed (shouldDisplay)
|
||||
// In addition, splits each line by \n. Each split line is given the same properties as the original line
|
||||
const displayProcessedLogs = computed(() => {
|
||||
return processedLogs.value.filter((l) => shouldDisplay(l))
|
||||
})
|
||||
|
||||
const processedLogs = computed(() => {
|
||||
// split based on newline and timestamp lookahead
|
||||
// (not just newline because of multiline messages)
|
||||
const splitPattern = /\n(?=(?:#|\[\d\d:\d\d:\d\d\]))/
|
||||
|
||||
const lines = logs.value[selectedLogIndex.value]?.stdout.split(splitPattern) || []
|
||||
const processed = []
|
||||
let id = 0
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
// Then split off of \n.
|
||||
// Lines that are not the first have prefix = null
|
||||
const text = getLineText(lines[i])
|
||||
const prefix = getLinePrefix(lines[i])
|
||||
const prefixColor = getLineColor(lines[i], true)
|
||||
const textColor = getLineColor(lines[i], false)
|
||||
const weight = getLineWeight(lines[i])
|
||||
const level = getLineLevel(lines[i])
|
||||
text.split('\n').forEach((line, index) => {
|
||||
processed.push({
|
||||
id: id,
|
||||
text: line,
|
||||
prefix: index === 0 ? prefix : null,
|
||||
prefixColor: prefixColor,
|
||||
textColor: textColor,
|
||||
weight: weight,
|
||||
level: level,
|
||||
})
|
||||
id += 1
|
||||
})
|
||||
}
|
||||
return processed
|
||||
})
|
||||
|
||||
async function getLiveLog() {
|
||||
if (route.params.id) {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
returnValue = emptyText.join('\n')
|
||||
} else {
|
||||
returnValue = await get_output_by_uuid(uuids[0]).catch(handleError)
|
||||
const logCursor = await get_latest_log_cursor(
|
||||
props.instance.path,
|
||||
currentLiveLogCursor.value
|
||||
).catch(handleError)
|
||||
if (logCursor.new_file) {
|
||||
currentLiveLog.value = ''
|
||||
}
|
||||
currentLiveLog.value = currentLiveLog.value + logCursor.output
|
||||
currentLiveLogCursor.value = logCursor.cursor
|
||||
returnValue = currentLiveLog.value
|
||||
}
|
||||
|
||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||
}
|
||||
return null
|
||||
@@ -112,9 +241,25 @@ async function getLiveLog() {
|
||||
|
||||
async function getLogs() {
|
||||
return (await get_logs(props.instance.path, true).catch(handleError)).reverse().map((log) => {
|
||||
log.name = dayjs(
|
||||
log.datetime_string.slice(0, 8) + 'T' + log.datetime_string.slice(9)
|
||||
).calendar()
|
||||
if (log.filename == 'latest.log') {
|
||||
log.name = 'Latest Log'
|
||||
} else {
|
||||
let filename = log.filename.split('.')[0]
|
||||
let day = dayjs(filename.slice(0, 10))
|
||||
if (day.isValid()) {
|
||||
if (day.isToday()) {
|
||||
log.name = 'Today'
|
||||
} else if (day.isYesterday()) {
|
||||
log.name = 'Yesterday'
|
||||
} else {
|
||||
log.name = day.format('MMMM D, YYYY')
|
||||
}
|
||||
// Displays as "Today-1", "Today-2", etc, matching minecraft log naming but with the date
|
||||
log.name = log.name + filename.slice(10)
|
||||
} else {
|
||||
log.name = filename
|
||||
}
|
||||
}
|
||||
log.stdout = 'Loading...'
|
||||
return log
|
||||
})
|
||||
@@ -152,29 +297,127 @@ watch(selectedLogIndex, async (newIndex) => {
|
||||
|
||||
if (logs.value.length > 1 && newIndex !== 0) {
|
||||
logs.value[newIndex].stdout = 'Loading...'
|
||||
logs.value[newIndex].stdout = await get_output_by_datetime(
|
||||
logs.value[newIndex].stdout = await get_output_by_filename(
|
||||
props.instance.path,
|
||||
logs.value[newIndex].datetime_string
|
||||
logs.value[newIndex].filename
|
||||
).catch(handleError)
|
||||
}
|
||||
})
|
||||
|
||||
if (logs.value.length >= 1) {
|
||||
if (logs.value.length > 1 && !props.playing) {
|
||||
selectedLogIndex.value = 1
|
||||
} else {
|
||||
selectedLogIndex.value = 0
|
||||
}
|
||||
|
||||
const deleteLog = async () => {
|
||||
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
||||
let deleteIndex = selectedLogIndex.value
|
||||
selectedLogIndex.value = deleteIndex - 1
|
||||
await delete_logs_by_datetime(
|
||||
props.instance.path,
|
||||
logs.value[deleteIndex].datetime_string
|
||||
).catch(handleError)
|
||||
await delete_logs_by_filename(props.instance.path, logs.value[deleteIndex].filename).catch(
|
||||
handleError
|
||||
)
|
||||
await setLogs()
|
||||
}
|
||||
}
|
||||
|
||||
const clearLiveLog = async () => {
|
||||
currentLiveLog.value = ''
|
||||
// does not reset cursor
|
||||
}
|
||||
|
||||
const isLineLevel = (text, level) => {
|
||||
if ((text.includes('/INFO') || text.includes('[System] [CHAT]')) && level === 'info') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (text.includes('/WARN') && level === 'warn') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (text.includes('/DEBUG') && level === 'debug') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (text.includes('/TRACE') && level === 'trace') {
|
||||
return true
|
||||
}
|
||||
|
||||
const errorTriggers = ['/ERROR', 'Exception:', ':?]', 'Error', '[thread', ' at']
|
||||
if (level === 'error') {
|
||||
for (const trigger of errorTriggers) {
|
||||
if (text.includes(trigger)) return true
|
||||
}
|
||||
}
|
||||
|
||||
if (text.trim()[0] === '#' && level === 'comment') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const getLineWeight = (text) => {
|
||||
if (
|
||||
!logsColored ||
|
||||
isLineLevel(text, 'info') ||
|
||||
isLineLevel(text, 'debug') ||
|
||||
isLineLevel(text, 'trace')
|
||||
) {
|
||||
return 'normal'
|
||||
}
|
||||
|
||||
if (isLineLevel(text, 'error') || isLineLevel(text, 'warn')) {
|
||||
return 'bold'
|
||||
}
|
||||
}
|
||||
|
||||
const getLineLevel = (text) => {
|
||||
for (const level of levels) {
|
||||
if (isLineLevel(text, level.toLowerCase())) {
|
||||
return level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getLineColor = (text, prefix) => {
|
||||
if (isLineLevel(text, 'comment')) {
|
||||
return 'var(--color-green)'
|
||||
}
|
||||
|
||||
if (!logsColored || text.includes('[System] [CHAT]')) {
|
||||
return 'var(--color-white)'
|
||||
}
|
||||
if (
|
||||
(isLineLevel(text, 'info') || isLineLevel(text, 'debug') || isLineLevel(text, 'trace')) &&
|
||||
prefix
|
||||
) {
|
||||
return 'var(--color-blue)'
|
||||
}
|
||||
if (isLineLevel(text, 'warn')) {
|
||||
return 'var(--color-orange)'
|
||||
}
|
||||
if (isLineLevel(text, 'error')) {
|
||||
return 'var(--color-red)'
|
||||
}
|
||||
}
|
||||
|
||||
const getLinePrefix = (text) => {
|
||||
if (text.includes(']:')) {
|
||||
return text.split(']:')[0] + ']:'
|
||||
}
|
||||
}
|
||||
|
||||
const getLineText = (text) => {
|
||||
if (text.includes(']:')) {
|
||||
if (text.split(']:').length > 2) {
|
||||
return text.split(']:').slice(1).join(']:')
|
||||
}
|
||||
return text.split(']:')[1]
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
function handleUserScroll() {
|
||||
if (!isAutoScrolling.value) {
|
||||
userScrolled.value = true
|
||||
@@ -185,19 +428,14 @@ interval.value = setInterval(async () => {
|
||||
if (logs.value.length > 0) {
|
||||
logs.value[0] = await getLiveLog()
|
||||
|
||||
const scroll = logContainer.value.getScroll()
|
||||
// Allow resetting of userScrolled if the user scrolls to the bottom
|
||||
if (selectedLogIndex.value === 0) {
|
||||
if (
|
||||
logContainer.value.scrollTop + logContainer.value.offsetHeight >=
|
||||
logContainer.value.scrollHeight - 10
|
||||
)
|
||||
userScrolled.value = false
|
||||
|
||||
if (scroll.end >= logContainer.value.$el.scrollHeight - 10) userScrolled.value = false
|
||||
if (!userScrolled.value) {
|
||||
await nextTick()
|
||||
isAutoScrolling.value = true
|
||||
logContainer.value.scrollTop =
|
||||
logContainer.value.scrollHeight - logContainer.value.offsetHeight
|
||||
logContainer.value.scrollToItem(displayProcessedLogs.value.length - 1)
|
||||
setTimeout(() => (isAutoScrolling.value = false), 50)
|
||||
}
|
||||
}
|
||||
@@ -206,9 +444,13 @@ interval.value = setInterval(async () => {
|
||||
|
||||
const unlistenProcesses = await process_listener(async (e) => {
|
||||
if (e.event === 'launched') {
|
||||
currentLiveLog.value = ''
|
||||
currentLiveLogCursor.value = 0
|
||||
selectedLogIndex.value = 0
|
||||
}
|
||||
if (e.event === 'finished') {
|
||||
currentLiveLog.value = ''
|
||||
currentLiveLogCursor.value = 0
|
||||
userScrolled.value = false
|
||||
await setLogs()
|
||||
selectedLogIndex.value = 1
|
||||
@@ -216,11 +458,11 @@ const unlistenProcesses = await process_listener(async (e) => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
logContainer.value.addEventListener('scroll', handleUserScroll)
|
||||
logContainer.value.$el.addEventListener('scroll', handleUserScroll)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
logContainer.value.removeEventListener('scroll', handleUserScroll)
|
||||
logContainer.value.$el.removeEventListener('scroll', handleUserScroll)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
clearInterval(interval.value)
|
||||
@@ -257,7 +499,9 @@ onUnmounted(() => {
|
||||
color: var(--color-contrast);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 1.5rem;
|
||||
overflow: auto;
|
||||
overflow-x: auto; /* Enables horizontal scrolling */
|
||||
overflow-y: hidden; /* Disables vertical scrolling on this wrapper */
|
||||
white-space: nowrap; /* Keeps content on a single line */
|
||||
white-space: normal;
|
||||
color-scheme: dark;
|
||||
|
||||
@@ -265,4 +509,37 @@ onUnmounted(() => {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-checkbox {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1rem;
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
}
|
||||
.filter-group {
|
||||
display: flex;
|
||||
padding: 0.6rem;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
:deep(.vue-recycle-scroller__item-wrapper) {
|
||||
overflow: visible; /* Enables horizontal scrolling */
|
||||
}
|
||||
|
||||
.scroller {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.user {
|
||||
height: 32%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,21 +26,25 @@
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
v-if="isPackLinked"
|
||||
v-tooltip="'Modpack is up to date'"
|
||||
:disabled="updatingModpack || !canUpdatePack"
|
||||
v-if="canUpdatePack"
|
||||
:disabled="installing"
|
||||
color="secondary"
|
||||
@click="updateModpack"
|
||||
@click="modpackVersionModal.show()"
|
||||
>
|
||||
<UpdatedIcon />
|
||||
{{ updatingModpack ? 'Updating' : 'Update modpack' }}
|
||||
{{ installing ? 'Updating' : 'Update modpack' }}
|
||||
</Button>
|
||||
<Button v-else @click="exportModal.show()">
|
||||
<Button v-else-if="!isPackLocked" @click="exportModal.show()">
|
||||
<PackageIcon />
|
||||
Export modpack
|
||||
</Button>
|
||||
<Button v-if="!isPackLocked && projects.some((m) => m.outdated)" @click="updateAll">
|
||||
<UpdatedIcon />
|
||||
Update all
|
||||
</Button>
|
||||
|
||||
<DropdownButton
|
||||
v-if="!isPackLinked"
|
||||
v-if="!isPackLocked"
|
||||
:options="['search', 'from_file']"
|
||||
default-value="search"
|
||||
name="add-content-dropdown"
|
||||
@@ -107,9 +111,9 @@
|
||||
<ShareIcon />
|
||||
Share
|
||||
</Button>
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to remove mods' : ''">
|
||||
<div v-tooltip="isPackLocked ? 'Unlock this instance to remove mods' : ''">
|
||||
<Button
|
||||
:disabled="isPackLinked"
|
||||
:disabled="isPackLocked"
|
||||
class="transparent trash"
|
||||
@click="deleteWarning.show()"
|
||||
@mouseover="selectedOption = 'Delete'"
|
||||
@@ -118,20 +122,20 @@
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to update mods' : ''">
|
||||
<div v-tooltip="isPackLocked ? 'Unlock this instance to update mods' : ''">
|
||||
<Button
|
||||
:disabled="isPackLinked || offline"
|
||||
:disabled="isPackLocked || offline"
|
||||
class="transparent update"
|
||||
@click="updateAll()"
|
||||
@click="updateSelected()"
|
||||
@mouseover="selectedOption = 'Update'"
|
||||
>
|
||||
<UpdatedIcon />
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to toggle mods' : ''">
|
||||
<div v-tooltip="isPackLocked ? 'Unlock this instance to toggle mods' : ''">
|
||||
<Button
|
||||
:disabled="isPackLinked"
|
||||
:disabled="isPackLocked"
|
||||
class="transparent"
|
||||
@click="toggleSelected()"
|
||||
@mouseover="selectedOption = 'Toggle'"
|
||||
@@ -232,21 +236,18 @@
|
||||
<span v-tooltip="`${mod.version}`">{{ mod.version }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text manage">
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to remove mods.' : ''">
|
||||
<Button
|
||||
v-tooltip="'Remove project'"
|
||||
:disabled="isPackLinked"
|
||||
icon-only
|
||||
@click="removeMod(mod)"
|
||||
>
|
||||
<div v-tooltip="isPackLocked ? 'Unlock this instance to remove mods.' : 'Remove project'">
|
||||
<Button :disabled="isPackLocked" icon-only @click="removeMod(mod)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<AnimatedLogo v-if="mod.updating" class="btn icon-only updating-indicator"></AnimatedLogo>
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to update mods.' : ''">
|
||||
<div
|
||||
v-else
|
||||
v-tooltip="isPackLocked ? 'Unlock this instance to update mods.' : 'Update project'"
|
||||
>
|
||||
<Button
|
||||
v-tooltip="'Update project'"
|
||||
:disabled="!mod.outdated || offline || isPackLinked"
|
||||
:disabled="!mod.outdated || offline || isPackLocked"
|
||||
icon-only
|
||||
@click="updateProject(mod)"
|
||||
>
|
||||
@@ -254,10 +255,10 @@
|
||||
<CheckIcon v-else />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-tooltip="isPackLinked ? 'Unpair this instance to toggle mods.' : ''">
|
||||
<div v-tooltip="isPackLocked ? 'Unlock this instance to toggle mods.' : ''">
|
||||
<input
|
||||
id="switch-1"
|
||||
:disabled="isPackLinked"
|
||||
:disabled="isPackLocked"
|
||||
autocomplete="off"
|
||||
type="checkbox"
|
||||
class="switch stylized-toggle"
|
||||
@@ -268,7 +269,7 @@
|
||||
<Button
|
||||
v-tooltip="`Show ${mod.file_name}`"
|
||||
icon-only
|
||||
@click="showProfileInFolder(mod.path)"
|
||||
@click="highlightModInProfile(instance.path, mod.path)"
|
||||
>
|
||||
<FolderOpenIcon />
|
||||
</Button>
|
||||
@@ -301,6 +302,14 @@
|
||||
</DropdownButton>
|
||||
</div>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="projects.length > 0"
|
||||
:page="currentPage"
|
||||
:count="Math.ceil(search.length / 20)"
|
||||
class="pagination-after"
|
||||
:link-function="(page) => `?page=${page}`"
|
||||
@switch-page="switchPage"
|
||||
/>
|
||||
<Modal ref="deleteWarning" header="Are you sure?">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
@@ -349,6 +358,12 @@
|
||||
share-text="Check out the projects I'm using in my modpack!"
|
||||
/>
|
||||
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
|
||||
<ModpackVersionModal
|
||||
v-if="instance.metadata.linked_data"
|
||||
ref="modpackVersionModal"
|
||||
:instance="instance"
|
||||
:versions="props.versions"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
@@ -385,7 +400,6 @@ import {
|
||||
remove_project,
|
||||
toggle_disable_project,
|
||||
update_all,
|
||||
update_managed_modrinth,
|
||||
update_project,
|
||||
} from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
@@ -393,9 +407,10 @@ import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -418,20 +433,24 @@ const props = defineProps({
|
||||
return false
|
||||
},
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const projects = ref([])
|
||||
const selectionMap = ref(new Map())
|
||||
const showingOptions = ref(false)
|
||||
const isPackLinked = computed(() => {
|
||||
return props.instance.metadata.linked_data
|
||||
const isPackLocked = computed(() => {
|
||||
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked
|
||||
})
|
||||
const canUpdatePack = computed(() => {
|
||||
if (!props.instance.metadata.linked_data) return false
|
||||
return props.instance.metadata.linked_data.version_id !== props.instance.modrinth_update_version
|
||||
})
|
||||
const exportModal = ref(null)
|
||||
|
||||
console.log(props.instance)
|
||||
const initProjects = (initInstance) => {
|
||||
projects.value = []
|
||||
if (!initInstance || !initInstance.projects) return
|
||||
@@ -508,6 +527,9 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
|
||||
const searchFilter = ref('')
|
||||
const selectAll = ref(false)
|
||||
const selectedProjectType = ref('All')
|
||||
@@ -661,6 +683,7 @@ const selectUpdatable = () => {
|
||||
|
||||
const updateProject = async (mod) => {
|
||||
mod.updating = true
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
mod.path = await update_project(props.instance.path, mod.path).catch(handleError)
|
||||
mod.updating = false
|
||||
|
||||
@@ -779,6 +802,14 @@ const toggleSelected = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const updateSelected = async () => {
|
||||
const promises = []
|
||||
for (const project of functionValues.value) {
|
||||
if (project.outdated) promises.push(updateProject(project))
|
||||
}
|
||||
await Promise.all(promises).catch(handleError)
|
||||
}
|
||||
|
||||
const enableAll = async () => {
|
||||
for (const project of functionValues.value) {
|
||||
if (project.disabled) {
|
||||
@@ -828,13 +859,6 @@ const handleContentOptionClick = async (args) => {
|
||||
}
|
||||
}
|
||||
|
||||
const updatingModpack = ref(false)
|
||||
const updateModpack = async () => {
|
||||
updatingModpack.value = true
|
||||
await update_managed_modrinth(props.instance.path).catch(handleError)
|
||||
updatingModpack.value = false
|
||||
}
|
||||
|
||||
watch(selectAll, () => {
|
||||
for (const [key, value] of Array.from(selectionMap.value)) {
|
||||
if (value !== selectAll.value) {
|
||||
@@ -1152,4 +1176,8 @@ onUnmounted(() => {
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-after {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,6 +8,56 @@
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="removeProfile"
|
||||
/>
|
||||
<Modal
|
||||
ref="modalConfirmUnlock"
|
||||
header="Are you sure you want to unlock this instance?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-delete">
|
||||
<div
|
||||
class="markdown-body"
|
||||
v-html="
|
||||
'If you proceed, you will not be able to re-lock it without using the `Reinstall modpack` button.'
|
||||
"
|
||||
/>
|
||||
<div class="input-group push-right">
|
||||
<button class="btn" @click="$refs.modalConfirmUnlock.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="action_disabled" @click="unlockProfile">
|
||||
<LockIcon />
|
||||
Unlock
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
ref="modalConfirmUnpair"
|
||||
header="Are you sure you want to unpair this instance?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-delete">
|
||||
<div
|
||||
class="markdown-body"
|
||||
v-html="
|
||||
'If you proceed, you will not be able to re-pair it without creating an entirely new instance.'
|
||||
"
|
||||
/>
|
||||
<div class="input-group push-right">
|
||||
<button class="btn" @click="$refs.modalConfirmUnpair.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="action_disabled" @click="unpairProfile">
|
||||
<XIcon />
|
||||
Unpair
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
ref="changeVersionsModal"
|
||||
header="Change instance versions"
|
||||
@@ -191,7 +241,7 @@
|
||||
<Slider
|
||||
v-model="memory.maximum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:min="8"
|
||||
:max="maxMemory"
|
||||
:step="1"
|
||||
unit="mb"
|
||||
@@ -298,22 +348,110 @@
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card v-if="instance.metadata.linked_data">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Modpack</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="general-modpack-info">
|
||||
<span class="label__description">
|
||||
<strong>Modpack: </strong> {{ instance.metadata.name }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
<strong>Version: </strong>
|
||||
{{
|
||||
installedVersionData.name.charAt(0).toUpperCase() + installedVersionData.name.slice(1)
|
||||
}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div v-if="!isPackLocked" class="adjacent-input">
|
||||
<Card class="unlocked-instance">
|
||||
This is an unlocked instance. There may be unexpected behaviour unintended by the modpack
|
||||
creator.
|
||||
</Card>
|
||||
</div>
|
||||
<div v-else class="adjacent-input">
|
||||
<label for="unlock-profile">
|
||||
<span class="label__title">Unlock instance</span>
|
||||
<span class="label__description">
|
||||
Allows modifications to the instance, which allows you to add projects to the modpack. The
|
||||
pack will remain linked, and you can still change versions. Only mods listed in the
|
||||
modpack will be modified on version changes.
|
||||
</span>
|
||||
</label>
|
||||
<Button id="unlock-profile" @click="$refs.modalConfirmUnlock.show()">
|
||||
<LockIcon /> Unlock
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="unpair-profile">
|
||||
<span class="label__title">Unpair instance</span>
|
||||
<span class="label__description">
|
||||
Removes the link to an external Modrinth modpack on the instance. This allows you to edit
|
||||
modpacks you download through the browse page but you will not be able to update the
|
||||
instance from a new version of a modpack if you do this.
|
||||
</span>
|
||||
</label>
|
||||
<Button id="unpair-profile" @click="$refs.modalConfirmUnpair.show()">
|
||||
<XIcon /> Unpair
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="change-modpack-version">
|
||||
<span class="label__title">Change modpack version</span>
|
||||
<span class="label__description">
|
||||
Changes to another version of the modpack, allowing upgrading or downgrading. This will
|
||||
replace all files marked as relevant to the modpack.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<Button
|
||||
id="change-modpack-version"
|
||||
:disabled="inProgress || installing"
|
||||
@click="modpackVersionModal.show()"
|
||||
>
|
||||
<SwapIcon />
|
||||
Change modpack version
|
||||
</Button>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="repair-modpack">
|
||||
<span class="label__title">Reinstall modpack</span>
|
||||
<span class="label__description">
|
||||
Removes all projects and reinstalls Modrinth modpack. Use this to fix unexpected behaviour
|
||||
if your instance is diverging from the Modrinth modpack. This also re-locks the instance.
|
||||
</span>
|
||||
</label>
|
||||
<Button id="repair-modpack" color="highlight" :disabled="offline" @click="repairModpack">
|
||||
<DownloadIcon /> Reinstall
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Instance management</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div v-if="instance.metadata.linked_data" class="adjacent-input">
|
||||
<label for="repair-profile">
|
||||
<span class="label__title">Unpair instance</span>
|
||||
<div v-if="instance.install_stage == 'installed'" class="adjacent-input">
|
||||
<label for="duplicate-profile">
|
||||
<span class="label__title">Duplicate instance</span>
|
||||
<span class="label__description">
|
||||
Removes the link to an external modpack on the instance. This allows you to edit modpacks
|
||||
you download through the browse page but you will not be able to update the instance from
|
||||
a new version of a modpack if you do this.
|
||||
Creates another copy of the instance, including saves, configs, mods, and everything.
|
||||
</span>
|
||||
</label>
|
||||
<Button id="repair-profile" @click="unpairProfile"> <XIcon /> Unpair </Button>
|
||||
<Button
|
||||
id="repair-profile"
|
||||
:disabled:="installing || inProgress || offline"
|
||||
@click="duplicateProfile"
|
||||
>
|
||||
<ClipboardCopyIcon /> Duplicate
|
||||
</Button>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="repair-profile">
|
||||
@@ -326,29 +464,12 @@
|
||||
<Button
|
||||
id="repair-profile"
|
||||
color="highlight"
|
||||
:disabled="repairing || offline"
|
||||
:disabled="installing || inProgress || repairing || offline"
|
||||
@click="repairProfile"
|
||||
>
|
||||
<HammerIcon /> Repair
|
||||
</Button>
|
||||
</div>
|
||||
<div v-if="props.instance.modrinth_update_version" class="adjacent-input">
|
||||
<label for="repair-profile">
|
||||
<span class="label__title">Reinstall modpack</span>
|
||||
<span class="label__description">
|
||||
Reinstalls Modrinth modpack and checks for corruption. Use this if your game is not
|
||||
launching due to your instance diverging from the Modrinth modpack.
|
||||
</span>
|
||||
</label>
|
||||
<Button
|
||||
id="repair-profile"
|
||||
color="highlight"
|
||||
:disabled="repairing || offline"
|
||||
@click="repairModpack"
|
||||
>
|
||||
<DownloadIcon /> Reinstall
|
||||
</Button>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="delete-profile">
|
||||
<span class="label__title">Delete instance</span>
|
||||
@@ -367,6 +488,12 @@
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
<ModpackVersionModal
|
||||
v-if="instance.metadata.linked_data"
|
||||
ref="modpackVersionModal"
|
||||
:instance="instance"
|
||||
:versions="props.versions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -383,14 +510,19 @@ import {
|
||||
DropdownSelect,
|
||||
XIcon,
|
||||
SaveIcon,
|
||||
LockIcon,
|
||||
HammerIcon,
|
||||
DownloadIcon,
|
||||
ModalConfirm,
|
||||
DownloadIcon,
|
||||
ClipboardCopyIcon,
|
||||
Button,
|
||||
} from 'omorphia'
|
||||
import { SwapIcon } from '@/assets/icons'
|
||||
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
duplicate,
|
||||
edit,
|
||||
edit_icon,
|
||||
get_optimal_jre_key,
|
||||
@@ -415,6 +547,7 @@ import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -427,6 +560,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
versions: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const themeStore = useTheming()
|
||||
@@ -435,6 +572,8 @@ const title = ref(props.instance.metadata.name)
|
||||
const icon = ref(props.instance.metadata.icon)
|
||||
const groups = ref(props.instance.metadata.groups)
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
|
||||
const instancesList = Object.values(await list(true))
|
||||
const availableGroups = ref([
|
||||
...instancesList.reduce((acc, obj) => {
|
||||
@@ -469,6 +608,9 @@ async function setIcon() {
|
||||
|
||||
const globalSettings = await get().catch(handleError)
|
||||
|
||||
const modalConfirmUnlock = ref(null)
|
||||
const modalConfirmUnpair = ref(null)
|
||||
|
||||
const javaSettings = props.instance.java ?? {}
|
||||
|
||||
const overrideJavaInstall = ref(!!javaSettings.override_version)
|
||||
@@ -496,6 +638,13 @@ const fullscreenSetting = ref(!!props.instance.fullscreen)
|
||||
|
||||
const unlinkModpack = ref(false)
|
||||
|
||||
const inProgress = ref(false)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.version_id)
|
||||
const installedVersionData = computed(() =>
|
||||
props.versions.find((version) => version.id === installedVersion.value)
|
||||
)
|
||||
|
||||
watch(
|
||||
[
|
||||
title,
|
||||
@@ -517,71 +666,78 @@ watch(
|
||||
unlinkModpack,
|
||||
],
|
||||
async () => {
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
name: title.value.trim().substring(0, 32) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.metadata.loader_version,
|
||||
linked_data: props.instance.metadata.linked_data,
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
|
||||
if (overrideJavaInstall.value) {
|
||||
if (javaInstall.value.path !== '') {
|
||||
editProfile.java.override_version = javaInstall.value
|
||||
editProfile.java.override_version.path = editProfile.java.override_version.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideJavaArgs.value) {
|
||||
if (javaArgs.value !== '') {
|
||||
editProfile.java.extra_arguments = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideEnvVars.value) {
|
||||
if (envVars.value !== '') {
|
||||
editProfile.java.custom_env_args = envVars.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideMemorySettings.value) {
|
||||
editProfile.memory = memory.value
|
||||
}
|
||||
|
||||
if (overrideWindowSettings.value) {
|
||||
editProfile.fullscreen = fullscreenSetting.value
|
||||
|
||||
if (!fullscreenSetting.value) {
|
||||
editProfile.resolution = resolution.value
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideHooks.value) {
|
||||
editProfile.hooks = hooks.value
|
||||
}
|
||||
|
||||
if (unlinkModpack.value) {
|
||||
editProfile.metadata.linked_data = null
|
||||
}
|
||||
|
||||
await edit(props.instance.path, editProfile)
|
||||
await edit(props.instance.path, editProfileObject.value)
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const editProfileObject = computed(() => {
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
name: title.value.trim().substring(0, 32) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.metadata.loader_version,
|
||||
linked_data: props.instance.metadata.linked_data,
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
|
||||
if (overrideJavaInstall.value) {
|
||||
if (javaInstall.value.path !== '') {
|
||||
editProfile.java.override_version = javaInstall.value
|
||||
editProfile.java.override_version.path = editProfile.java.override_version.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideJavaArgs.value) {
|
||||
if (javaArgs.value !== '') {
|
||||
editProfile.java.extra_arguments = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideEnvVars.value) {
|
||||
if (envVars.value !== '') {
|
||||
editProfile.java.custom_env_args = envVars.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideMemorySettings.value) {
|
||||
editProfile.memory = memory.value
|
||||
}
|
||||
|
||||
if (overrideWindowSettings.value) {
|
||||
editProfile.fullscreen = fullscreenSetting.value
|
||||
|
||||
if (!fullscreenSetting.value) {
|
||||
editProfile.resolution = resolution.value
|
||||
}
|
||||
}
|
||||
|
||||
if (overrideHooks.value) {
|
||||
editProfile.hooks = hooks.value
|
||||
}
|
||||
|
||||
if (unlinkModpack.value) {
|
||||
editProfile.metadata.linked_data = null
|
||||
}
|
||||
return editProfile
|
||||
})
|
||||
|
||||
const repairing = ref(false)
|
||||
|
||||
async function unpairProfile() {
|
||||
unlinkModpack.value = true
|
||||
async function duplicateProfile() {
|
||||
await duplicate(props.instance.path).catch(handleError)
|
||||
mixpanel_track('InstanceDuplicate', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
})
|
||||
}
|
||||
|
||||
async function repairProfile() {
|
||||
@@ -595,10 +751,30 @@ async function repairProfile() {
|
||||
})
|
||||
}
|
||||
|
||||
async function unpairProfile() {
|
||||
const editProfile = props.instance
|
||||
editProfile.metadata.linked_data = null
|
||||
await edit(props.instance.path, editProfile)
|
||||
installedVersion.value = null
|
||||
installedVersionData.value = null
|
||||
modalConfirmUnpair.value.hide()
|
||||
}
|
||||
|
||||
async function unlockProfile() {
|
||||
const editProfile = props.instance
|
||||
editProfile.metadata.linked_data.locked = false
|
||||
await edit(props.instance.path, editProfile)
|
||||
modalConfirmUnlock.value.hide()
|
||||
}
|
||||
|
||||
const isPackLocked = computed(() => {
|
||||
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked
|
||||
})
|
||||
|
||||
async function repairModpack() {
|
||||
repairing.value = true
|
||||
inProgress.value = true
|
||||
await update_repair_modrinth(props.instance.path).catch(handleError)
|
||||
repairing.value = false
|
||||
inProgress.value = false
|
||||
|
||||
mixpanel_track('InstanceRepair', {
|
||||
loader: props.instance.metadata.loader,
|
||||
@@ -711,12 +887,9 @@ const editing = ref(false)
|
||||
async function saveGvLoaderEdits() {
|
||||
editing.value = true
|
||||
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
game_version: gameVersion.value,
|
||||
loader: loader.value,
|
||||
},
|
||||
}
|
||||
let editProfile = editProfileObject.value
|
||||
editProfile.metadata.loader = loader.value
|
||||
editProfile.metadata.game_version = gameVersion.value
|
||||
|
||||
if (loader.value !== 'vanilla') {
|
||||
editProfile.metadata.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value]
|
||||
@@ -772,4 +945,39 @@ async function saveGvLoaderEdits() {
|
||||
:deep(button.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.unlocked-instance {
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
|
||||
.modal-delete {
|
||||
padding: var(--gap-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.markdown-body {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.confirmation-label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.confirmation-text {
|
||||
padding-right: 0.25ch;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.confirmation-input {
|
||||
input {
|
||||
width: 20rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-left: auto;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user