New features (#477)

* Linking/Unlinking, Dir changing, CDN

* Fixes #435

* Progress bar

* Create splashscreen.html

* Run lint

* add rust part

* remove splashscreen code

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Adrian O.V
2023-08-05 17:44:02 -04:00
committed by GitHub
parent 47e28d24c8
commit 5ee64f2705
14 changed files with 341 additions and 41 deletions

151
theseus_gui/dist/splashscreen.html vendored Normal file
View File

@@ -0,0 +1,151 @@
<body>
<div class="splashscreen">
<div class="splashscreen__icon">
<svg
class="rotate outer"
width="100%"
height="100%"
viewBox="0 0 590 591"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
>
<g transform="matrix(1,0,0,1,652.392,-0.400578)">
<g transform="matrix(4.16667,0,0,4.16667,-735.553,0)">
<g transform="matrix(0.24,0,0,0.24,0,0)">
<path
d="M134.44,316.535C145.027,441.531 249.98,539.829 377.711,539.829C474.219,539.829 557.724,483.712 597.342,402.371L645.949,419.197C599.165,520.543 496.595,590.954 377.711,590.954C221.751,590.954 93.869,469.779 83.161,316.535L134.44,316.535ZM83.946,265.645C99.012,116.762 224.88,0.401 377.711,0.401C540.678,0.401 672.987,132.71 672.987,295.677C672.987,321.817 669.583,347.168 663.194,371.313L614.709,354.529C619.381,335.689 621.862,315.971 621.862,295.677C621.862,160.926 512.461,51.526 377.711,51.526C253.133,51.526 150.223,145.03 135.392,265.645L83.946,265.645Z"
style="fill: var(--color-brand)"
/>
</g>
</g>
</g></svg
><svg
class="rotate inner"
width="100%"
height="100%"
viewBox="0 0 590 591"
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
>
<g transform="matrix(1,0,0,1,652.392,-0.400578)">
<g transform="matrix(4.16667,0,0,4.16667,-735.553,0)">
<g transform="matrix(0.24,0,0,0.24,0,0)">
<path
d="M376.933,153.568C298.44,153.644 234.735,217.396 234.735,295.909C234.735,374.47 298.516,438.251 377.077,438.251C381.06,438.251 385.005,438.087 388.914,437.764L403.128,487.517C394.611,488.667 385.912,489.261 377.077,489.261C270.363,489.261 183.725,402.623 183.725,295.909C183.725,189.195 270.363,102.557 377.077,102.557C379.723,102.557 382.357,102.611 384.983,102.717L376.933,153.568ZM435.127,111.438C513.515,136.114 570.428,209.418 570.428,295.909C570.428,375.976 521.655,444.742 452.22,474.093L438.063,424.541C486.142,401.687 519.418,352.653 519.418,295.909C519.418,234.923 480.981,182.843 427.029,162.593L435.127,111.438Z"
style="fill: var(--color-brand)"
/>
</g>
</g>
</g>
</svg>
<svg
width="100%"
height="100%"
viewBox="0 0 590 591"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
xmlns:serif="http://www.serif.com/"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
>
<g transform="matrix(1,0,0,1,652.392,-0.400578)">
<g transform="matrix(4.16667,0,0,4.16667,-735.553,0)">
<g transform="matrix(0.24,0,0,0.24,0,0)">
<path
d="M300.366,311.86L283.216,266.381L336.966,211.169L404.9,196.531L424.57,220.74L393.254,252.46L365.941,261.052L346.425,281.11L355.987,307.719L375.387,328.306L402.745,321.031L422.216,299.648L464.729,286.185L477.395,314.677L433.529,368.46L360.02,391.735L327.058,355.031L138.217,468.344C129.245,456.811 118.829,440.485 112.15,424.792L300.366,311.86Z"
style="fill: var(--color-brand)"
/>
</g>
</g>
<g transform="matrix(4.16667,0,0,4.16667,-735.553,0)">
<g transform="matrix(0.24,0,0,0.24,0,0)">
<path
d="M655.189,194.555L505.695,234.873C513.927,256.795 516.638,269.674 518.915,283.863L668.152,243.609C665.764,227.675 661.5,211.444 655.189,194.555Z"
style="fill: var(--color-brand)"
/>
</g>
</g>
</g>
</svg>
</div>
<div class="splashscreen__text">Loading...</div>
</div>
</body>
<style>
body {
font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--color-brand: #1bd96a;
background-color: #16181c;
}
.splashscreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
}
.splashscreen__text {
color: #fff;
font-size: 1.5rem;
font-weight: 700;
margin-top: 1rem;
}
.splashscreen__text::after {
content: '';
display: block;
width: 0;
height: 2px;
background-color: #fff;
animation: loading 2s ease-in-out infinite;
}
@keyframes loading {
from {
width: 0;
}
to {
width: 100%;
}
}
.splashscreen__icon {
height: 10rem;
width: 10rem;
}
.splashscreen__icon svg {
width: 10rem;
height: 10rem;
position: absolute;
}
.rotate {
animation: rotate 4s infinite linear;
}
.inner {
animation: rotate 6s infinite linear reverse;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -142,7 +142,7 @@ fn main() {
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
initialize_state, initialize_state,
is_dev, is_dev,
toggle_decorations toggle_decorations,
]); ]);
builder builder

View File

@@ -11,7 +11,7 @@
:src=" :src="
selectedAccount selectedAccount
? `https://mc-heads.net/avatar/${selectedAccount.id}/128` ? `https://mc-heads.net/avatar/${selectedAccount.id}/128`
: 'https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png' : 'https://launcher-files.modrinth.com/assets/steve_head.png'
" "
/> />
<div v-show="mode === 'expanded'" class="avatar-text"> <div v-show="mode === 'expanded'" class="avatar-text">

View File

@@ -188,6 +188,8 @@ const exportPack = async () => {
} }
.select-checkbox { .select-checkbox {
gap: var(--gap-sm);
button.checkbox { button.checkbox {
border: none; border: none;
} }

View File

@@ -181,6 +181,10 @@
: 'Select profiles to import' : 'Select profiles to import'
}} }}
</Button> </Button>
<ProgressBar
v-if="loading"
:progress="(importedProfiles / (totalProfiles + 0.0001)) * 100"
/>
</div> </div>
</div> </div>
</Modal> </Modal>
@@ -224,6 +228,7 @@ import {
get_importable_instances, get_importable_instances,
import_instance, import_instance,
} from '@/helpers/import.js' } from '@/helpers/import.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
const themeStore = useTheming() const themeStore = useTheming()
@@ -420,6 +425,8 @@ const profiles = ref(
) )
const loading = ref(false) const loading = ref(false)
const importedProfiles = ref(0)
const totalProfiles = ref(0)
const selectedProfileType = ref('MultiMC') const selectedProfileType = ref('MultiMC')
const profileOptions = ref([ const profileOptions = ref([
@@ -480,6 +487,10 @@ const setPath = () => {
} }
const next = async () => { const next = async () => {
importedProfiles.value = 0
totalProfiles.value = Array.from(profiles.value.values())
.map((profiles) => profiles.filter((profile) => profile.selected).length)
.reduce((a, b) => a + b, 0)
loading.value = true loading.value = true
for (const launcher of Array.from(profiles.value.entries()).map(([launcher, profiles]) => ({ for (const launcher of Array.from(profiles.value.entries()).map(([launcher, profiles]) => ({
launcher, launcher,
@@ -491,6 +502,7 @@ const next = async () => {
.catch(handleError) .catch(handleError)
.then(() => console.log(`Successfully Imported ${profile.name} from ${launcher.launcher}`)) .then(() => console.log(`Successfully Imported ${profile.name} from ${launcher.launcher}`))
profile.selected = false profile.selected = false
importedProfiles.value++
} }
} }
loading.value = false loading.value = false
@@ -628,6 +640,7 @@ const next = async () => {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center;
gap: var(--gap-md); gap: var(--gap-md);
.transparent { .transparent {

View File

@@ -119,7 +119,7 @@ const install = async (e) => {
'background-image': `url(${ 'background-image': `url(${
project.featured_gallery ?? project.featured_gallery ??
project.gallery[0] ?? project.gallery[0] ??
'https://cdn.discordapp.com/attachments/817413688771608587/1119143634319724564/image.png' 'https://launcher-files.modrinth.com/assets/maze-bg.png'
})`, })`,
'no-image': !project.featured_gallery && !project.gallery[0], 'no-image': !project.featured_gallery && !project.gallery[0],
}" }"

View File

@@ -1,17 +1,12 @@
<template> <template>
<div ref="button" class="button-base avatar-button" :class="{ highlighted: showDemo }"> <div ref="button" class="button-base avatar-button" :class="{ highlighted: showDemo }">
<Avatar <Avatar src="https://launcher-files.modrinth.com/assets/steve_head.png" />
src="https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png"
/>
</div> </div>
<transition name="fade"> <transition name="fade">
<div v-if="showDemo" class="card-section"> <div v-if="showDemo" class="card-section">
<Card ref="card" class="fake-account-card expanded highlighted"> <Card ref="card" class="fake-account-card expanded highlighted">
<div class="selected account"> <div class="selected account">
<Avatar <Avatar size="xs" src="https://launcher-files.modrinth.com/assets/steve_head.png" />
size="xs"
src="https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png"
/>
<div> <div>
<h4>Modrinth</h4> <h4>Modrinth</h4>
<p>Selected</p> <p>Selected</p>

View File

@@ -65,7 +65,7 @@ defineProps({
> >
<Avatar <Avatar
size="sm" size="sm"
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png" src="https://launcher-files.modrinth.com/assets/default_profile.png"
alt="Mod card" alt="Mod card"
class="mod-image" class="mod-image"
/> />

View File

@@ -63,7 +63,7 @@ defineProps({
> >
<Avatar <Avatar
size="sm" size="sm"
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png" src="https://launcher-files.modrinth.com/assets/maze-bg.png"
alt="Mod card" alt="Mod card"
class="mod-image" class="mod-image"
/> />
@@ -79,7 +79,7 @@ defineProps({
<div <div
class="banner no-image" class="banner no-image"
:style="{ :style="{
'background-image': `url(https://cdn.discordapp.com/attachments/817413688771608587/1119143634319724564/image.png)`, 'background-image': `url(https://launcher-files.modrinth.com/assets/maze-bg.png)`,
}" }"
> >
<div class="badges"> <div class="badges">
@@ -111,7 +111,7 @@ defineProps({
<Avatar <Avatar
class="icon" class="icon"
size="sm" size="sm"
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png" src="https://launcher-files.modrinth.com/assets/default_profile.png"
/> />
<div class="title"> <div class="title">
<div class="title-text">Example Project</div> <div class="title-text">Example Project</div>

View File

@@ -220,7 +220,7 @@ defineProps({
<Card v-for="project in 20" :key="project" class="search-card button-base"> <Card v-for="project in 20" :key="project" class="search-card button-base">
<div class="icon"> <div class="icon">
<Avatar <Avatar
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png" src="https://launcher-files.modrinth.com/assets/default_profile.png"
size="md" size="md"
class="search-icon" class="search-icon"
/> />

View File

@@ -57,7 +57,7 @@ const openUrl = async () => {
<template> <template>
<div class="login-card"> <div class="login-card">
<img <img
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png" src="https://launcher-files.modrinth.com/assets/default_profile.png"
class="logo" class="logo"
alt="Minecraft art" alt="Minecraft art"
/> />

View File

@@ -1,13 +1,26 @@
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { Card, Slider, DropdownSelect, Toggle, Modal, LogOutIcon, LogInIcon } from 'omorphia' import {
Card,
Slider,
DropdownSelect,
Toggle,
Modal,
LogOutIcon,
LogInIcon,
Button,
BoxIcon,
FolderSearchIcon,
UpdatedIcon,
} from 'omorphia'
import { handleError, useTheming } from '@/store/state' import { handleError, useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings' import { change_config_dir, get, set } from '@/helpers/settings'
import { get_max_memory } from '@/helpers/jre' import { get_max_memory } from '@/helpers/jre'
import { get as getCreds, logout } from '@/helpers/mr_auth.js' import { get as getCreds, logout } from '@/helpers/mr_auth.js'
import JavaSelector from '@/components/ui/JavaSelector.vue' import JavaSelector from '@/components/ui/JavaSelector.vue'
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue' import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel' import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel'
import { open } from '@tauri-apps/api/dialog'
const pageOptions = ['Home', 'Library'] const pageOptions = ['Home', 'Library']
@@ -24,6 +37,7 @@ fetchSettings.javaArgs = fetchSettings.custom_java_args.join(' ')
fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ') fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ')
const settings = ref(fetchSettings) const settings = ref(fetchSettings)
const settingsDir = ref(settings.value.loaded_config_dir)
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024)) const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
watch( watch(
@@ -91,6 +105,19 @@ async function signInAfter() {
loginScreenModal.value.hide() loginScreenModal.value.hide()
credentials.value = await getCreds().catch(handleError) credentials.value = await getCreds().catch(handleError)
} }
async function findLauncherDir() {
const newDir = await open({ multiple: false, directory: true })
if (newDir) {
settingsDir.value = newDir
await refreshDir()
}
}
async function refreshDir() {
await change_config_dir(settingsDir.value)
}
</script> </script>
<template> <template>
@@ -98,7 +125,7 @@ async function signInAfter() {
<Card> <Card>
<div class="label"> <div class="label">
<h3> <h3>
<span class="label__title size-card-header">Account</span> <span class="label__title size-card-header">General settings</span>
</h3> </h3>
</div> </div>
<Modal <Modal
@@ -125,6 +152,25 @@ async function signInAfter() {
<LogInIcon /> Sign in <LogInIcon /> Sign in
</button> </button>
</div> </div>
<label for="theme">
<span class="label__title">App directory</span>
<span class="label__description">
The directory where the launcher stores all of its files.
</span>
</label>
<div class="app-directory">
<div class="iconified-input">
<BoxIcon />
<input id="appDir" v-model="settingsDir" type="text" class="input" />
<Button @click="findLauncherDir">
<FolderSearchIcon />
</Button>
</div>
<Button large @click="refreshDir">
<UpdatedIcon />
Refresh
</Button>
</div>
</Card> </Card>
<Card> <Card>
<div class="label"> <div class="label">
@@ -455,4 +501,19 @@ async function signInAfter() {
} }
} }
} }
.app-directory {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--gap-sm);
.iconified-input {
flex-grow: 1;
input {
flex-basis: auto;
}
}
}
</style> </style>

View File

@@ -1,11 +1,19 @@
<template> <template>
<Card v-if="projects.length > 0" class="mod-card"> <Card
v-if="projects.length > 0"
class="mod-card"
:class="{ static: instance.metadata.linked_data }"
>
<div class="second-row"> <div class="second-row">
<Chips <Chips
v-if="Object.keys(selectableProjectTypes).length > 1" v-if="Object.keys(selectableProjectTypes).length > 1"
v-model="selectedProjectType" v-model="selectedProjectType"
:items="Object.keys(selectableProjectTypes)" :items="Object.keys(selectableProjectTypes)"
/> />
<Button v-if="canUpdatePack" color="secondary" @click="updateModpack">
<UpdatedIcon />
Update modpack
</Button>
</div> </div>
<div class="card-row"> <div class="card-row">
<div class="iconified-input"> <div class="iconified-input">
@@ -46,7 +54,7 @@
<div> <div>
<div class="table"> <div class="table">
<div class="table-row table-head" :class="{ 'show-options': selected.length > 0 }"> <div class="table-row table-head" :class="{ 'show-options': selected.length > 0 }">
<div class="table-cell table-text"> <div v-if="!instance.metadata.linked_data" class="table-cell table-text">
<Checkbox v-model="selectAll" class="select-checkbox" /> <Checkbox v-model="selectAll" class="select-checkbox" />
</div> </div>
<div v-if="selected.length === 0" class="table-cell table-text name-cell actions-cell"> <div v-if="selected.length === 0" class="table-cell table-text name-cell actions-cell">
@@ -55,19 +63,23 @@
<DropdownIcon v-if="sortColumn === 'Name'" :class="{ down: ascending }" /> <DropdownIcon v-if="sortColumn === 'Name'" :class="{ down: ascending }" />
</Button> </Button>
</div> </div>
<div v-if="selected.length === 0" class="table-cell table-text"> <div v-if="selected.length === 0" class="table-cell table-text version">
<Button class="transparent" @click="sortProjects('Version')"> <Button class="transparent" @click="sortProjects('Version')">
Version Version
<DropdownIcon v-if="sortColumn === 'Version'" :class="{ down: ascending }" /> <DropdownIcon v-if="sortColumn === 'Version'" :class="{ down: ascending }" />
</Button> </Button>
</div> </div>
<div v-if="selected.length === 0" class="table-cell table-text actions-cell"> <div v-if="selected.length === 0" class="table-cell table-text actions-cell">
<Button class="transparent" @click="sortProjects('Enabled')"> <Button
v-if="!instance.metadata.linked_data"
class="transparent"
@click="sortProjects('Enabled')"
>
Actions Actions
<DropdownIcon v-if="sortColumn === 'Enabled'" :class="{ down: ascending }" /> <DropdownIcon v-if="sortColumn === 'Enabled'" :class="{ down: ascending }" />
</Button> </Button>
</div> </div>
<div v-else class="options table-cell name-cell"> <div v-else-if="!instance.metadata.linked_data" class="options table-cell name-cell">
<Button <Button
class="transparent share" class="transparent share"
@click="() => (showingOptions = !showingOptions)" @click="() => (showingOptions = !showingOptions)"
@@ -110,7 +122,10 @@
</Button> </Button>
</div> </div>
</div> </div>
<div v-if="showingOptions && selected.length > 0" class="more-box"> <div
v-if="showingOptions && selected.length > 0 && !instance.metadata.linked_data"
class="more-box"
>
<section v-if="selectedOption === 'Share'" class="options"> <section v-if="selectedOption === 'Share'" class="options">
<Button class="transparent" @click="shareNames()"> <Button class="transparent" @click="shareNames()">
<TextInputIcon /> <TextInputIcon />
@@ -171,7 +186,7 @@
class="table-row" class="table-row"
@contextmenu.prevent.stop="(c) => handleRightClick(c, mod)" @contextmenu.prevent.stop="(c) => handleRightClick(c, mod)"
> >
<div class="table-cell table-text"> <div v-if="!instance.metadata.linked_data" class="table-cell table-text checkbox">
<Checkbox <Checkbox
:model-value="selectionMap.get(mod.path)" :model-value="selectionMap.get(mod.path)"
class="select-checkbox" class="select-checkbox"
@@ -196,19 +211,24 @@
<span v-tooltip="`${mod.name}`" class="title">{{ mod.name }}</span> <span v-tooltip="`${mod.name}`" class="title">{{ mod.name }}</span>
</div> </div>
</div> </div>
<div class="table-cell table-text"> <div class="table-cell table-text version">
<span v-tooltip="`${mod.version}`">{{ mod.version }}</span> <span v-tooltip="`${mod.version}`">{{ mod.version }}</span>
</div> </div>
<div class="table-cell table-text manage"> <div class="table-cell table-text manage">
<Button v-tooltip="'Remove project'" icon-only @click="removeMod(mod)"> <Button
v-if="!instance.metadata.linked_data"
v-tooltip="'Remove project'"
icon-only
@click="removeMod(mod)"
>
<TrashIcon /> <TrashIcon />
</Button> </Button>
<AnimatedLogo <AnimatedLogo
v-if="mod.updating" v-if="mod.updating && !instance.metadata.linked_data"
class="btn icon-only updating-indicator" class="btn icon-only updating-indicator"
></AnimatedLogo> ></AnimatedLogo>
<Button <Button
v-else v-else-if="!instance.metadata.linked_data"
v-tooltip="'Update project'" v-tooltip="'Update project'"
:disabled="!mod.outdated || offline" :disabled="!mod.outdated || offline"
icon-only icon-only
@@ -218,6 +238,7 @@
<CheckIcon v-else /> <CheckIcon v-else />
</Button> </Button>
<input <input
v-if="!instance.metadata.linked_data"
id="switch-1" id="switch-1"
autocomplete="off" autocomplete="off"
type="checkbox" type="checkbox"
@@ -341,9 +362,11 @@ import { useRouter } from 'vue-router'
import { import {
add_project_from_path, add_project_from_path,
get, get,
is_managed_modrinth,
remove_project, remove_project,
toggle_disable_project, toggle_disable_project,
update_all, update_all,
update_managed_modrinth,
update_project, update_project,
} from '@/helpers/profile.js' } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
@@ -380,6 +403,7 @@ const props = defineProps({
const projects = ref([]) const projects = ref([])
const selectionMap = ref(new Map()) const selectionMap = ref(new Map())
const showingOptions = ref(false) const showingOptions = ref(false)
const canUpdatePack = ref(await is_managed_modrinth(props.instance.path))
const initProjects = (initInstance) => { const initProjects = (initInstance) => {
projects.value = [] projects.value = []
@@ -776,6 +800,10 @@ const handleContentOptionClick = async (args) => {
} }
} }
const updateModpack = async () => {
await update_managed_modrinth(props.instance.path).catch(handleError)
}
watch(selectAll, () => { watch(selectAll, () => {
for (const [key, value] of Array.from(selectionMap.value)) { for (const [key, value] of Array.from(selectionMap.value)) {
if (value !== selectAll.value) { if (value !== selectAll.value) {
@@ -791,6 +819,7 @@ const unlisten = await listen('tauri://file-drop', async (event) => {
} }
initProjects(await get(props.instance.path).catch(handleError)) initProjects(await get(props.instance.path).catch(handleError))
}) })
onUnmounted(() => { onUnmounted(() => {
unlisten() unlisten()
}) })
@@ -827,6 +856,26 @@ onUnmounted(() => {
} }
} }
.static {
.table-row {
grid-template-areas: 'manage name version';
grid-template-columns: 4.25rem 1fr 1fr;
}
.name-cell {
grid-area: name;
}
.version {
grid-area: version;
}
.manage {
justify-content: center;
grid-area: manage;
}
}
.table-cell { .table-cell {
align-items: center; align-items: center;
} }

View File

@@ -85,7 +85,14 @@
<label for="project-name"> <label for="project-name">
<span class="label__title">Name</span> <span class="label__title">Name</span>
</label> </label>
<input id="profile-name" v-model="title" autocomplete="off" maxlength="80" type="text" /> <input
id="profile-name"
v-model="title"
autocomplete="off"
maxlength="80"
type="text"
:disabled="instance.metadata.linked_data"
/>
<div class="adjacent-input"> <div class="adjacent-input">
<label for="edit-versions"> <label for="edit-versions">
@@ -289,6 +296,17 @@
<span class="label__title size-card-header">Instance management</span> <span class="label__title size-card-header">Instance management</span>
</h3> </h3>
</div> </div>
<div v-if="instance.metadata.linked_data" class="adjacent-input">
<label for="repair-profile">
<span class="label__title">Unpair 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.
</span>
</label>
<Button id="repair-profile" @click="unpairProfile"> <XIcon /> Unpair </Button>
</div>
<div class="adjacent-input"> <div class="adjacent-input">
<label for="repair-profile"> <label for="repair-profile">
<span class="label__title">Repair instance</span> <span class="label__title">Repair instance</span>
@@ -297,14 +315,14 @@
launching due to launcher-related errors. launching due to launcher-related errors.
</span> </span>
</label> </label>
<button <Button
id="repair-profile" id="repair-profile"
class="btn btn-highlight" color="highlight"
:disabled="repairing || offline" :disabled="repairing || offline"
@click="repairProfile" @click="repairProfile"
> >
<HammerIcon /> Repair <HammerIcon /> Repair
</button> </Button>
</div> </div>
<div v-if="props.instance.modrinth_update_version" class="adjacent-input"> <div v-if="props.instance.modrinth_update_version" class="adjacent-input">
<label for="repair-profile"> <label for="repair-profile">
@@ -314,16 +332,15 @@
launching due to your instance diverging from the Modrinth modpack. launching due to your instance diverging from the Modrinth modpack.
</span> </span>
</label> </label>
<button <Button
id="repair-profile" id="repair-profile"
class="btn btn-highlight" color="highlight"
:disabled="repairing || offline" :disabled="repairing || offline"
@click="repairModpack" @click="repairModpack"
> >
<DownloadIcon /> Reinstall <DownloadIcon /> Reinstall
</button> </Button>
</div> </div>
<div class="adjacent-input"> <div class="adjacent-input">
<label for="delete-profile"> <label for="delete-profile">
<span class="label__title">Delete instance</span> <span class="label__title">Delete instance</span>
@@ -332,14 +349,14 @@
no way to recover it. no way to recover it.
</span> </span>
</label> </label>
<button <Button
id="delete-profile" id="delete-profile"
class="btn btn-danger" color="danger"
:disabled="removing" :disabled="removing"
@click="$refs.modal_confirm.show()" @click="$refs.modal_confirm.show()"
> >
<TrashIcon /> Delete <TrashIcon /> Delete
</button> </Button>
</div> </div>
</Card> </Card>
</template> </template>
@@ -361,6 +378,7 @@ import {
HammerIcon, HammerIcon,
DownloadIcon, DownloadIcon,
ModalConfirm, ModalConfirm,
Button,
} from 'omorphia' } from 'omorphia'
import { Multiselect } from 'vue-multiselect' import { Multiselect } from 'vue-multiselect'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -465,6 +483,8 @@ const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
const fullscreenSetting = ref(!!props.instance.fullscreen) const fullscreenSetting = ref(!!props.instance.fullscreen)
const unlinkModpack = ref(false)
watch( watch(
[ [
title, title,
@@ -483,6 +503,7 @@ watch(
fullscreenSetting, fullscreenSetting,
overrideHooks, overrideHooks,
hooks, hooks,
unlinkModpack,
], ],
async () => { async () => {
const editProfile = { const editProfile = {
@@ -537,6 +558,10 @@ watch(
editProfile.hooks = hooks.value editProfile.hooks = hooks.value
} }
if (unlinkModpack.value) {
editProfile.metadata.linked_data = null
}
await edit(props.instance.path, editProfile) await edit(props.instance.path, editProfile)
}, },
{ deep: true } { deep: true }
@@ -544,6 +569,10 @@ watch(
const repairing = ref(false) const repairing = ref(false)
async function unpairProfile() {
unlinkModpack.value = true
}
async function repairProfile() { async function repairProfile() {
repairing.value = true repairing.value = true
await install(props.instance.path).catch(handleError) await install(props.instance.path).catch(handleError)