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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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![
initialize_state,
is_dev,
toggle_decorations
toggle_decorations,
]);
builder

View File

@ -11,7 +11,7 @@
:src="
selectedAccount
? `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">

View File

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

View File

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

View File

@ -119,7 +119,7 @@ const install = async (e) => {
'background-image': `url(${
project.featured_gallery ??
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],
}"

View File

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

View File

@ -65,7 +65,7 @@ defineProps({
>
<Avatar
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"
class="mod-image"
/>

View File

@ -63,7 +63,7 @@ defineProps({
>
<Avatar
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"
class="mod-image"
/>
@ -79,7 +79,7 @@ defineProps({
<div
class="banner no-image"
: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">
@ -111,7 +111,7 @@ defineProps({
<Avatar
class="icon"
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-text">Example Project</div>

View File

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

View File

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

View File

@ -1,13 +1,26 @@
<script setup>
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 { get, set } from '@/helpers/settings'
import { change_config_dir, get, set } from '@/helpers/settings'
import { get_max_memory } from '@/helpers/jre'
import { get as getCreds, logout } from '@/helpers/mr_auth.js'
import JavaSelector from '@/components/ui/JavaSelector.vue'
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel'
import { open } from '@tauri-apps/api/dialog'
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(' ')
const settings = ref(fetchSettings)
const settingsDir = ref(settings.value.loaded_config_dir)
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
watch(
@ -91,6 +105,19 @@ async function signInAfter() {
loginScreenModal.value.hide()
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>
<template>
@ -98,7 +125,7 @@ async function signInAfter() {
<Card>
<div class="label">
<h3>
<span class="label__title size-card-header">Account</span>
<span class="label__title size-card-header">General settings</span>
</h3>
</div>
<Modal
@ -125,6 +152,25 @@ async function signInAfter() {
<LogInIcon /> Sign in
</button>
</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>
<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>

View File

@ -1,11 +1,19 @@
<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">
<Chips
v-if="Object.keys(selectableProjectTypes).length > 1"
v-model="selectedProjectType"
:items="Object.keys(selectableProjectTypes)"
/>
<Button v-if="canUpdatePack" color="secondary" @click="updateModpack">
<UpdatedIcon />
Update modpack
</Button>
</div>
<div class="card-row">
<div class="iconified-input">
@ -46,7 +54,7 @@
<div>
<div class="table">
<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" />
</div>
<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 }" />
</Button>
</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')">
Version
<DropdownIcon v-if="sortColumn === 'Version'" :class="{ down: ascending }" />
</Button>
</div>
<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
<DropdownIcon v-if="sortColumn === 'Enabled'" :class="{ down: ascending }" />
</Button>
</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
class="transparent share"
@click="() => (showingOptions = !showingOptions)"
@ -110,7 +122,10 @@
</Button>
</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">
<Button class="transparent" @click="shareNames()">
<TextInputIcon />
@ -171,7 +186,7 @@
class="table-row"
@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
:model-value="selectionMap.get(mod.path)"
class="select-checkbox"
@ -196,19 +211,24 @@
<span v-tooltip="`${mod.name}`" class="title">{{ mod.name }}</span>
</div>
</div>
<div class="table-cell table-text">
<div class="table-cell table-text version">
<span v-tooltip="`${mod.version}`">{{ mod.version }}</span>
</div>
<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 />
</Button>
<AnimatedLogo
v-if="mod.updating"
v-if="mod.updating && !instance.metadata.linked_data"
class="btn icon-only updating-indicator"
></AnimatedLogo>
<Button
v-else
v-else-if="!instance.metadata.linked_data"
v-tooltip="'Update project'"
:disabled="!mod.outdated || offline"
icon-only
@ -218,6 +238,7 @@
<CheckIcon v-else />
</Button>
<input
v-if="!instance.metadata.linked_data"
id="switch-1"
autocomplete="off"
type="checkbox"
@ -341,9 +362,11 @@ import { useRouter } from 'vue-router'
import {
add_project_from_path,
get,
is_managed_modrinth,
remove_project,
toggle_disable_project,
update_all,
update_managed_modrinth,
update_project,
} from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
@ -380,6 +403,7 @@ const props = defineProps({
const projects = ref([])
const selectionMap = ref(new Map())
const showingOptions = ref(false)
const canUpdatePack = ref(await is_managed_modrinth(props.instance.path))
const initProjects = (initInstance) => {
projects.value = []
@ -776,6 +800,10 @@ const handleContentOptionClick = async (args) => {
}
}
const updateModpack = async () => {
await update_managed_modrinth(props.instance.path).catch(handleError)
}
watch(selectAll, () => {
for (const [key, value] of Array.from(selectionMap.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))
})
onUnmounted(() => {
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 {
align-items: center;
}

View File

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