Compare commits

..

10 Commits

Author SHA1 Message Date
Geometrically
afaec4b1bf Bump Theseus Version (#818)
* push to test on windows

* Fix windows sup

* Fix macos

* Fix back

* new resolver for windows testing

* Custom macos handling for some versions

* Fix 1.13+ broken

* fix arg parsing mac

* small winblows fix

* remove debug info; set meta url

* run lint + fix clippy

* Remove useless commnet
2023-10-21 13:08:47 -07:00
Carter
7fb8850071 reduce side effects during migration hydration (#803) 2023-10-15 22:12:26 -07:00
Carter
8ccc7dfcd2 Fix launcher duplicate profile function (#804) 2023-10-12 10:41:44 -07:00
ToBinio
da07d7328d fix google.svg (#795) 2023-10-09 10:34:56 -07:00
Jackson Kruger
772597ce2a Make export selection consistent between platforms and allow selecting which projects to export (#789)
* Experimenting with tests

* Overhaul handling of paths for pack files to always use standardized style

Also allows disabling export of all items

* Minor improvements

* Revert test things

* Minor tweaks

* Fix clippy warning
2023-10-09 10:34:19 -07:00
ToBinio
e76a7d57c0 fix categories duplicating (#773) 2023-10-06 19:59:18 -07:00
Jackson Kruger
ebc4da6c29 Use 'neoforge' instead of 'neo-forge' in mrpacks (#787) 2023-10-04 15:42:09 -07:00
Miraculixx
f73c112e07 Fixing bug report template - Wrong nesting (#756)
* Wrap string in quotes to support brackets

* Moving string to label
2023-10-02 10:28:45 -07:00
chaos
7fbc9fa357 Allow mods to be added to unlocked instances. (#760)
* Allow mods to be added to unlocked instances.

* Learn about optional chaining?.
2023-10-02 10:27:04 -07:00
chaos
6f8ffcaf35 Overflow and mrpack fixes, comfortable memory slider and more. (#762)
* Add overflow rules for certain elements.

* Round scrollbar corners to be more attractive.

* Avoid corners in avatar & fix white corner in logs

* Increase step to 64 for memory related inputs.

* Fix overflow on title in mod browse. Fixes #740

* Fix overflow in instance mod browsing.

* Add checks for instances without versions.
2023-10-02 10:26:22 -07:00
41 changed files with 426 additions and 255 deletions

View File

@@ -26,7 +26,8 @@ body:
validations:
required: false
- type: textarea
attributes: System information
attributes:
label: System information
description: Add any information about what OS you are on (like Windows or Mac), and what version of the app you are using.
- type: textarea
attributes:

6
Cargo.lock generated
View File

@@ -4685,7 +4685,7 @@ dependencies = [
[[package]]
name = "theseus"
version = "0.5.4"
version = "0.6.0"
dependencies = [
"async-recursion",
"async-tungstenite",
@@ -4733,7 +4733,7 @@ dependencies = [
[[package]]
name = "theseus_cli"
version = "0.5.4"
version = "0.6.0"
dependencies = [
"argh",
"color-eyre",
@@ -4760,7 +4760,7 @@ dependencies = [
[[package]]
name = "theseus_gui"
version = "0.5.4"
version = "0.6.0"
dependencies = [
"chrono",
"cocoa",

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus"
version = "0.5.4"
version = "0.6.0"
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2018"

View File

@@ -68,10 +68,14 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
}
// Update player info from bearer token
let player_info = hydra::stages::player_info::fetch_info(&credentials.access_token).await.map_err(|_err| {
crate::ErrorKind::HydraError("No Minecraft account for your profile. Make sure you own the game and have set a username through the official Minecraft launcher."
.to_string())
})?;
let player_info =
hydra::stages::player_info::fetch_info(&credentials.access_token)
.await
.map_err(|_err| {
crate::ErrorKind::HydraError(
"No Minecraft account for your profile. Please try again or contact support in our Discord for help!".to_string(),
)
})?;
credentials.username = player_info.name;
users.insert(&credentials).await?;

View File

@@ -13,7 +13,7 @@ impl Default for PlayerInfo {
fn default() -> Self {
Self {
id: "606e2ff0ed7748429d6ce1d3321c7838".to_string(),
name: String::from("???"),
name: String::from("Unknown"),
}
}
}

View File

@@ -127,7 +127,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
// removes the old installation of java
if let Some(file) = archive.file_names().next() {
if let Some(dir) = file.split("/").next() {
if let Some(dir) = file.split('/').next() {
let path = path.join(dir);
if path.exists() {

View File

@@ -245,8 +245,12 @@ async fn import_atlauncher_unmanaged(
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?
{
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
.await?;
crate::launcher::install_minecraft(
&profile_val,
Some(loading_bar),
false,
)
.await?;
{
let state = State::get().await?;
let mut file_watcher = state.file_watcher.write().await;

View File

@@ -199,8 +199,12 @@ pub async fn import_curseforge(
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?
{
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
.await?;
crate::launcher::install_minecraft(
&profile_val,
Some(loading_bar),
false,
)
.await?;
{
let state = State::get().await?;

View File

@@ -112,8 +112,12 @@ pub async fn import_gdlauncher(
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?
{
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
.await?;
crate::launcher::install_minecraft(
&profile_val,
Some(loading_bar),
false,
)
.await?;
{
let state = State::get().await?;
let mut file_watcher = state.file_watcher.write().await;

View File

@@ -323,8 +323,12 @@ async fn import_mmc_unmanaged(
if let Some(profile_val) =
crate::api::profile::get(&profile_path, None).await?
{
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
.await?;
crate::launcher::install_minecraft(
&profile_val,
Some(loading_bar),
false,
)
.await?;
{
let state = State::get().await?;
let mut file_watcher = state.file_watcher.write().await;

View File

@@ -10,7 +10,7 @@ use crate::util::fetch::{
fetch, fetch_advanced, fetch_json, write_cached_icon,
};
use crate::util::io;
use crate::State;
use crate::{InnerProjectPathUnix, State};
use reqwest::Method;
use serde::{Deserialize, Serialize};
@@ -33,7 +33,7 @@ pub struct PackFormat {
#[derive(Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PackFile {
pub path: String,
pub path: InnerProjectPathUnix,
pub hashes: HashMap<PackFileHash, String>,
pub env: Option<HashMap<EnvType, SideType>>,
pub downloads: Vec<String>,
@@ -66,12 +66,21 @@ pub enum EnvType {
}
#[derive(Serialize, Deserialize, Clone, Copy, Hash, PartialEq, Eq, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum PackDependency {
#[serde(rename = "forge")]
Forge,
#[serde(rename = "neoforge")]
#[serde(alias = "neo-forge")]
NeoForge,
#[serde(rename = "fabric-loader")]
FabricLoader,
#[serde(rename = "quilt-loader")]
QuiltLoader,
#[serde(rename = "minecraft")]
Minecraft,
}

View File

@@ -189,10 +189,7 @@ pub async fn install_zipped_mrpack_files(
.await?;
drop(creds);
// Convert windows path to unix path.
// .mrpacks no longer generate windows paths, but this is here for backwards compatibility before this was fixed
// https://github.com/modrinth/theseus/issues/595
let project_path = project.path.replace('\\', "/");
let project_path = project.path.to_string();
let path =
std::path::Path::new(&project_path).components().next();
@@ -286,8 +283,12 @@ pub async fn install_zipped_mrpack_files(
}
if let Some(profile_val) = profile::get(&profile_path, None).await? {
crate::launcher::install_minecraft(&profile_val, Some(loading_bar))
.await?;
crate::launcher::install_minecraft(
&profile_val,
Some(loading_bar),
false,
)
.await?;
State::sync().await?;
}
@@ -403,7 +404,10 @@ pub async fn remove_all_related_files(
// Iterate over all Modrinth project file paths in the json, and remove them
// (There should be few, but this removes any files the .mrpack intended as Modrinth projects but were unrecognized)
for file in pack.files {
let path = profile_path.get_full_path().await?.join(file.path);
let path: PathBuf = profile_path
.get_full_path()
.await?
.join(file.path.to_string());
if path.exists() {
io::remove_file(&path).await?;
}

View File

@@ -125,7 +125,7 @@ pub async fn profile_create(
}
if !skip_install_profile.unwrap_or(false) {
crate::launcher::install_minecraft(&profile, None).await?;
crate::launcher::install_minecraft(&profile, None, false).await?;
}
State::sync().await?;
@@ -163,6 +163,7 @@ pub async fn profile_create_from_creator(
pub async fn profile_create_from_duplicate(
copy_from: ProfilePathId,
) -> crate::Result<ProfilePathId> {
// Original profile
let profile = profile::get(&copy_from, None).await?.ok_or_else(|| {
ErrorKind::UnmanagedProfileError(copy_from.to_string())
})?;
@@ -190,7 +191,13 @@ pub async fn profile_create_from_duplicate(
)
.await?;
crate::launcher::install_minecraft(&profile, Some(bar)).await?;
let duplicated_profile =
profile::get(&profile_path_id, None).await?.ok_or_else(|| {
ErrorKind::UnmanagedProfileError(profile_path_id.to_string())
})?;
crate::launcher::install_minecraft(&duplicated_profile, Some(bar), false)
.await?;
{
let state = State::get().await?;
let mut file_watcher = state.file_watcher.write().await;

View File

@@ -8,7 +8,7 @@ use crate::pack::install_from::{
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
};
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
use crate::state::{ProjectMetadata, SideType};
use crate::state::{InnerProjectPathUnix, ProjectMetadata, SideType};
use crate::util::fetch;
use crate::util::io::{self, IOError};
@@ -25,8 +25,9 @@ use async_zip::tokio::write::ZipFileWriter;
use async_zip::{Compression, ZipEntryBuilder};
use serde_json::json;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::{
future::Future,
path::{Path, PathBuf},
@@ -280,9 +281,9 @@ pub async fn list(
/// Installs/Repairs a profile
#[tracing::instrument]
pub async fn install(path: &ProfilePathId) -> crate::Result<()> {
pub async fn install(path: &ProfilePathId, force: bool) -> crate::Result<()> {
if let Some(profile) = get(path, None).await? {
crate::launcher::install_minecraft(&profile, None).await?;
crate::launcher::install_minecraft(&profile, None, force).await?;
} else {
return Err(crate::ErrorKind::UnmanagedProfileError(path.to_string())
.as_error());
@@ -570,7 +571,7 @@ pub async fn remove_project(
pub async fn export_mrpack(
profile_path: &ProfilePathId,
export_path: PathBuf,
included_overrides: Vec<String>, // which folders to include in the overrides
included_export_candidates: Vec<String>, // which folders/files to include in the export
version_id: Option<String>,
description: Option<String>,
_name: Option<String>,
@@ -585,8 +586,8 @@ pub async fn export_mrpack(
))
})?;
// remove .DS_Store files from included_overrides
let included_overrides = included_overrides
// remove .DS_Store files from included_export_candidates
let included_export_candidates = included_export_candidates
.into_iter()
.filter(|x| {
if let Some(f) = PathBuf::from(x).file_name() {
@@ -607,13 +608,17 @@ pub async fn export_mrpack(
// Create mrpack json configuration file
let version_id = version_id.unwrap_or("1.0.0".to_string());
let packfile =
let mut packfile =
create_mrpack_json(&profile, version_id, description).await?;
let modrinth_path_list = get_modrinth_pack_list(&packfile);
let included_candidates_set =
HashSet::<_>::from_iter(included_export_candidates.iter());
packfile.files.retain(|f| {
included_candidates_set.contains(&f.path.get_topmost_two_components())
});
// Build vec of all files in the folder
let mut path_list = Vec::new();
build_folder(profile_base_path, &mut path_list).await?;
add_all_recursive_folder_paths(profile_base_path, &mut path_list).await?;
// Initialize loading bar
let loading_bar = init_loading(
@@ -631,38 +636,13 @@ pub async fn export_mrpack(
for path in path_list {
emit_loading(&loading_bar, 1.0, None).await?;
// Get local path of file, relative to profile folder
let relative_path = path.strip_prefix(profile_base_path)?;
// Get highest level folder pair ('a/b' in 'a/b/c', 'a' in 'a')
// We only go one layer deep for the sake of not having a huge list of overrides
let topmost_two = relative_path.iter().take(2).collect::<Vec<_>>();
// a,b => a/b
// a => a
let topmost = match topmost_two.len() {
2 => PathBuf::from(topmost_two[0]).join(topmost_two[1]),
1 => PathBuf::from(topmost_two[0]),
_ => {
return Err(crate::ErrorKind::OtherError(
"No topmost folder found".to_string(),
)
.into())
}
}
.to_string_lossy()
.to_string();
if !included_overrides.contains(&topmost) {
continue;
}
let relative_path: std::borrow::Cow<str> =
relative_path.to_string_lossy();
let relative_path = relative_path.replace('\\', "/");
let relative_path = relative_path.trim_start_matches('/').to_string();
if modrinth_path_list.contains(&relative_path) {
let relative_path = ProjectPathId::from_fs_path(&path)
.await?
.get_inner_path_unix();
if packfile.files.iter().any(|f| f.path == relative_path)
|| !included_candidates_set
.contains(&relative_path.get_topmost_two_components())
{
continue;
}
@@ -696,30 +676,28 @@ pub async fn export_mrpack(
Ok(())
}
// Given a folder path, populate a Vec of all the subfolders
// Intended to be used for finding potential override folders
// Given a folder path, populate a Vec of all the subfolders and files, at most 2 layers deep
// profile
// -- folder1
// -- folder2
// -- innerfolder
// -- innerfile
// -- folder2file
// -- file1
// => [folder1, folder2]
// => [folder1, folder2/innerfolder, folder2/folder2file, file1]
#[tracing::instrument]
pub async fn get_potential_override_folders(
profile_path: ProfilePathId,
) -> crate::Result<Vec<PathBuf>> {
pub async fn get_pack_export_candidates(
profile_path: &ProfilePathId,
) -> crate::Result<Vec<InnerProjectPathUnix>> {
// First, get a dummy mrpack json for the files within
let profile: Profile =
get(&profile_path, None).await?.ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Tried to export a nonexistent or unloaded profile at path {}!",
profile_path
))
})?;
// dummy mrpack to get pack list
let mrpack = create_mrpack_json(&profile, "0".to_string(), None).await?;
let mrpack_files = get_modrinth_pack_list(&mrpack);
let profile: Profile = get(profile_path, None).await?.ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Tried to export a nonexistent or unloaded profile at path {}!",
profile_path
))
})?;
let mut path_list: Vec<PathBuf> = Vec::new();
let mut path_list: Vec<InnerProjectPathUnix> = Vec::new();
let profile_base_dir = profile.get_profile_full_path().await?;
let mut read_dir = io::read_dir(&profile_base_dir).await?;
@@ -738,16 +716,16 @@ pub async fn get_potential_override_folders(
.map_err(|e| IOError::with_path(e, &profile_base_dir))?
{
let path: PathBuf = entry.path();
let name = path.strip_prefix(&profile_base_dir)?.to_path_buf();
if !mrpack_files.contains(&name.to_string_lossy().to_string()) {
path_list.push(name);
if let Ok(project_path) =
ProjectPathId::from_fs_path(&path).await
{
path_list.push(project_path.get_inner_path_unix());
}
}
} else {
// One layer of files/folders if its a file
let name = path.strip_prefix(&profile_base_dir)?.to_path_buf();
if !mrpack_files.contains(&name.to_string_lossy().to_string()) {
path_list.push(name);
if let Ok(project_path) = ProjectPathId::from_fs_path(&path).await {
path_list.push(project_path.get_inner_path_unix());
}
}
}
@@ -934,19 +912,6 @@ pub async fn try_update_playtime(path: &ProfilePathId) -> crate::Result<()> {
res
}
fn get_modrinth_pack_list(packfile: &PackFormat) -> Vec<String> {
packfile
.files
.iter()
.map(|f| {
let path = PathBuf::from(f.path.clone());
let name = path.to_string_lossy();
let name = name.replace('\\', "/");
name.trim_start_matches('/').to_string()
})
.collect::<Vec<String>>()
}
/// Creates a json configuration for a .mrpack zipped file
// Version ID of uploaded version (ie 1.1.5), not the unique identifying ID of the version (nvrqJg44)
#[tracing::instrument(skip_all)]
@@ -997,7 +962,7 @@ pub async fn create_mrpack_json(
.projects
.iter()
.filter_map(|(mod_path, project)| {
let path: String = mod_path.get_inner_path_unix().ok()?;
let path = mod_path.get_inner_path_unix();
// Only Modrinth projects have a modrinth metadata field for the modrinth.json
Some(Ok(match project.metadata {
@@ -1087,7 +1052,7 @@ fn sanitize_loader_version_string(s: &str, loader: PackDependency) -> &str {
// Given a folder path, populate a Vec of all the files in the folder, recursively
#[async_recursion::async_recursion]
pub async fn build_folder(
pub async fn add_all_recursive_folder_paths(
path: &Path,
path_list: &mut Vec<PathBuf>,
) -> crate::Result<()> {
@@ -1099,7 +1064,7 @@ pub async fn build_folder(
{
let path = entry.path();
if path.is_dir() {
build_folder(&path, path_list).await?;
add_all_recursive_folder_paths(&path, path_list).await?;
} else {
path_list.push(path);
}

View File

@@ -1,6 +1,6 @@
//! Minecraft CLI argument logic
// TODO: Rafactor this section
use super::{auth::Credentials, parse_rule};
use super::auth::Credentials;
use crate::launcher::parse_rules;
use crate::{
state::{MemorySettings, WindowSize},
util::{io::IOError, platform::classpath_separator},
@@ -23,12 +23,13 @@ pub fn get_class_paths(
libraries: &[Library],
client_path: &Path,
java_arch: &str,
minecraft_updated: bool,
) -> crate::Result<String> {
let mut cps = libraries
.iter()
.filter_map(|library| {
if let Some(rules) = &library.rules {
if !rules.iter().any(|x| parse_rule(x, java_arch)) {
if !parse_rules(rules, java_arch, minecraft_updated) {
return None;
}
}
@@ -335,7 +336,7 @@ where
}
}
Argument::Ruled { rules, value } => {
if rules.iter().any(|x| parse_rule(x, java_arch)) {
if parse_rules(rules, java_arch, true) {
match value {
ArgumentValue::Single(arg) => {
parsed_arguments.push(parse_function(

View File

@@ -1,5 +1,6 @@
//! Downloader for Minecraft data
use crate::launcher::parse_rules;
use crate::state::CredentialsStore;
use crate::{
event::{
@@ -26,11 +27,13 @@ pub async fn download_minecraft(
version: &GameVersionInfo,
loading_bar: &LoadingBarId,
java_arch: &str,
force: bool,
minecraft_updated: bool,
) -> crate::Result<()> {
tracing::info!("Downloading Minecraft version {}", version.id);
// 5
let assets_index =
download_assets_index(st, version, Some(loading_bar)).await?;
download_assets_index(st, version, Some(loading_bar), force).await?;
let amount = if version
.processors
@@ -45,9 +48,9 @@ pub async fn download_minecraft(
tokio::try_join! {
// Total loading sums to 90/60
download_client(st, version, Some(loading_bar)), // 10
download_assets(st, version.assets == "legacy", &assets_index, Some(loading_bar), amount), // 40
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar), amount, java_arch) // 40
download_client(st, version, Some(loading_bar), force), // 10
download_assets(st, version.assets == "legacy", &assets_index, Some(loading_bar), amount, force), // 40
download_libraries(st, version.libraries.as_slice(), &version.id, Some(loading_bar), amount, java_arch, force, minecraft_updated) // 40
}?;
tracing::info!("Done downloading Minecraft!");
@@ -105,6 +108,7 @@ pub async fn download_client(
st: &State,
version_info: &GameVersionInfo,
loading_bar: Option<&LoadingBarId>,
force: bool,
) -> crate::Result<()> {
let version = &version_info.id;
tracing::debug!("Locating client for version {version}");
@@ -123,7 +127,7 @@ pub async fn download_client(
.await
.join(format!("{version}.jar"));
if !path.exists() {
if !path.exists() || force {
let bytes = fetch(
&client_download.url,
Some(&client_download.sha1),
@@ -148,6 +152,7 @@ pub async fn download_assets_index(
st: &State,
version: &GameVersionInfo,
loading_bar: Option<&LoadingBarId>,
force: bool,
) -> crate::Result<AssetsIndex> {
tracing::debug!("Loading assets index");
let path = st
@@ -156,7 +161,7 @@ pub async fn download_assets_index(
.await
.join(format!("{}.json", &version.asset_index.id));
let res = if path.exists() {
let res = if path.exists() && !force {
io::read(path)
.err_into::<crate::Error>()
.await
@@ -183,6 +188,7 @@ pub async fn download_assets(
index: &AssetsIndex,
loading_bar: Option<&LoadingBarId>,
loading_amount: f64,
force: bool,
) -> crate::Result<()> {
tracing::debug!("Loading assets");
let num_futs = index.objects.len();
@@ -206,7 +212,7 @@ pub async fn download_assets(
let fetch_cell = OnceCell::<bytes::Bytes>::new();
tokio::try_join! {
async {
if !resource_path.exists() {
if !resource_path.exists() || force {
let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore, &CredentialsStore(None)))
.await?;
@@ -216,13 +222,14 @@ pub async fn download_assets(
Ok::<_, crate::Error>(())
},
async {
if with_legacy {
let resource_path = st.directories.legacy_assets_dir().await.join(
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
);
if with_legacy && !resource_path.exists() || force {
let resource = fetch_cell
.get_or_try_init(|| fetch(&url, Some(hash), &st.fetch_semaphore, &CredentialsStore(None)))
.await?;
let resource_path = st.directories.legacy_assets_dir().await.join(
name.replace('/', &String::from(std::path::MAIN_SEPARATOR))
);
write(&resource_path, resource, &st.io_semaphore).await?;
tracing::trace!("Fetched legacy asset with hash {hash}");
}
@@ -239,6 +246,7 @@ pub async fn download_assets(
#[tracing::instrument(skip(st, libraries))]
#[theseus_macros::debug_pin]
#[allow(clippy::too_many_arguments)]
pub async fn download_libraries(
st: &State,
libraries: &[Library],
@@ -246,6 +254,8 @@ pub async fn download_libraries(
loading_bar: Option<&LoadingBarId>,
loading_amount: f64,
java_arch: &str,
force: bool,
minecraft_updated: bool,
) -> crate::Result<()> {
tracing::debug!("Loading libraries");
@@ -258,7 +268,7 @@ pub async fn download_libraries(
stream::iter(libraries.iter())
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
if let Some(rules) = &library.rules {
if !rules.iter().any(|x| super::parse_rule(x, java_arch)) {
if !parse_rules(rules, java_arch, minecraft_updated) {
tracing::trace!("Skipped library {}", &library.name);
return Ok(());
}
@@ -270,7 +280,7 @@ pub async fn download_libraries(
let path = st.directories.libraries_dir().await.join(&artifact_path);
match library.downloads {
_ if path.exists() => Ok(()),
_ if path.exists() && !force => Ok(()),
Some(d::minecraft::LibraryDownloads {
artifact: Some(ref artifact),
..

View File

@@ -13,7 +13,7 @@ use crate::{
};
use chrono::Utc;
use daedalus as d;
use daedalus::minecraft::VersionInfo;
use daedalus::minecraft::{RuleAction, VersionInfo};
use st::Profile;
use std::collections::HashMap;
use std::{process::Stdio, sync::Arc};
@@ -25,14 +25,48 @@ mod args;
pub mod auth;
pub mod download;
// All nones -> disallowed
// 1+ true -> allowed
// 1+ false -> disallowed
#[tracing::instrument]
pub fn parse_rule(rule: &d::minecraft::Rule, java_version: &str) -> bool {
pub fn parse_rules(
rules: &[d::minecraft::Rule],
java_version: &str,
minecraft_updated: bool,
) -> bool {
let mut x = rules
.iter()
.map(|x| parse_rule(x, java_version, minecraft_updated))
.collect::<Vec<Option<bool>>>();
if rules
.iter()
.all(|x| matches!(x.action, RuleAction::Disallow))
{
x.push(Some(true))
}
!(x.iter().any(|x| x == &Some(false)) || x.iter().all(|x| x.is_none()))
}
// if anything is disallowed, it should NOT be included
// if anything is not disallowed, it shouldn't factor in final result
// if anything is not allowed, it shouldn't factor in final result
// if anything is allowed, it should be included
#[tracing::instrument]
pub fn parse_rule(
rule: &d::minecraft::Rule,
java_version: &str,
minecraft_updated: bool,
) -> Option<bool> {
use d::minecraft::{Rule, RuleAction};
let res = match rule {
Rule {
os: Some(ref os), ..
} => crate::util::platform::os_rule(os, java_version),
} => {
crate::util::platform::os_rule(os, java_version, minecraft_updated)
}
Rule {
features: Some(ref features),
..
@@ -44,12 +78,24 @@ pub fn parse_rule(rule: &d::minecraft::Rule, java_version: &str) -> bool {
|| !features.is_quick_play_realms.unwrap_or(true)
|| !features.is_quick_play_singleplayer.unwrap_or(true)
}
_ => false,
_ => return Some(true),
};
match rule.action {
RuleAction::Allow => res,
RuleAction::Disallow => !res,
RuleAction::Allow => {
if res {
Some(true)
} else {
None
}
}
RuleAction::Disallow => {
if res {
Some(false)
} else {
None
}
}
}
}
@@ -102,6 +148,7 @@ pub async fn get_java_version_from_profile(
pub async fn install_minecraft(
profile: &Profile,
existing_loading_bar: Option<LoadingBarId>,
repairing: bool,
) -> crate::Result<()> {
let sync_projects = existing_loading_bar.is_some();
let loading_bar = init_or_edit_loading(
@@ -133,15 +180,23 @@ pub async fn install_minecraft(
&io::canonicalize(&profile.get_profile_full_path().await?)?;
let metadata = state.metadata.read().await;
let version = metadata
let version_index = metadata
.minecraft
.versions
.iter()
.find(|it| it.id == profile.metadata.game_version)
.position(|it| it.id == profile.metadata.game_version)
.ok_or(crate::ErrorKind::LauncherError(format!(
"Invalid game version: {}",
profile.metadata.game_version
)))?;
let version = &metadata.minecraft.versions[version_index];
let minecraft_updated = version_index
<= metadata
.minecraft
.versions
.iter()
.position(|x| x.id == "22w16a")
.unwrap_or(0);
let version_jar = profile
.metadata
@@ -156,7 +211,7 @@ pub async fn install_minecraft(
&state,
version,
profile.metadata.loader_version.as_ref(),
None,
Some(repairing),
Some(&loading_bar),
)
.await?;
@@ -185,6 +240,8 @@ pub async fn install_minecraft(
&version_info,
&loading_bar,
&java_version.architecture,
repairing,
minecraft_updated,
)
.await?;
@@ -325,7 +382,7 @@ pub async fn launch_minecraft(
}
if profile.install_stage != ProfileInstallStage::Installed {
install_minecraft(profile, None).await?;
install_minecraft(profile, None, false).await?;
}
let state = State::get().await?;
@@ -334,15 +391,23 @@ pub async fn launch_minecraft(
let instance_path = profile.get_profile_full_path().await?;
let instance_path = &io::canonicalize(instance_path)?;
let version = metadata
let version_index = metadata
.minecraft
.versions
.iter()
.find(|it| it.id == profile.metadata.game_version)
.position(|it| it.id == profile.metadata.game_version)
.ok_or(crate::ErrorKind::LauncherError(format!(
"Invalid game version: {}",
profile.metadata.game_version
)))?;
let version = &metadata.minecraft.versions[version_index];
let minecraft_updated = version_index
<= metadata
.minecraft
.versions
.iter()
.position(|x| x.id == "22w16a")
.unwrap_or(0);
let version_jar = profile
.metadata
@@ -418,6 +483,7 @@ pub async fn launch_minecraft(
version_info.libraries.as_slice(),
&client_path,
&java_version.architecture,
minecraft_updated,
)?,
&version_jar,
*memory,

View File

@@ -22,4 +22,5 @@ pub use api::*;
pub use error::*;
pub use event::{EventState, LoadingBar, LoadingBarType};
pub use logger::start_logger;
pub use state::InnerProjectPathUnix;
pub use state::State;

View File

@@ -383,7 +383,7 @@ pub async fn init_watcher() -> crate::Result<Debouncer<RecommendedWatcher>> {
// At this point, new_path is the path to the profile, and subfile is whether it's a subfile of the profile or not
let profile_path_id =
ProfilePathId::new(&PathBuf::from(
ProfilePathId::new(PathBuf::from(
new_path.file_name().unwrap_or_default(),
));

View File

@@ -322,29 +322,6 @@ pub async fn create_account(
get_creds_from_res(response, semaphore).await
}
pub async fn login_minecraft(
flow: &str,
semaphore: &FetchSemaphore,
) -> crate::Result<ModrinthCredentialsResult> {
let resp = fetch_advanced(
Method::POST,
&format!("{MODRINTH_API_URL}auth/login/minecraft"),
None,
Some(serde_json::json!({
"flow": flow,
})),
None,
None,
semaphore,
&CredentialsStore(None),
)
.await?;
let response = serde_json::from_slice::<HashMap<String, Value>>(&resp)?;
get_result_from_res("session", response, semaphore).await
}
pub async fn refresh_credentials(
credentials_store: &mut CredentialsStore,
semaphore: &FetchSemaphore,

View File

@@ -72,8 +72,8 @@ impl ProfilePathId {
}
// Create a new ProfilePathId from a relative path
pub fn new(path: &Path) -> Self {
ProfilePathId(PathBuf::from(path))
pub fn new(path: impl Into<PathBuf>) -> Self {
ProfilePathId(path.into())
}
pub async fn get_full_path(&self) -> crate::Result<PathBuf> {
@@ -95,6 +95,45 @@ impl std::fmt::Display for ProfilePathId {
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
#[serde(into = "RawProjectPath", from = "RawProjectPath")]
pub struct InnerProjectPathUnix(pub String);
impl InnerProjectPathUnix {
pub fn get_topmost_two_components(&self) -> String {
self.to_string()
.split('/')
.take(2)
.collect::<Vec<_>>()
.join("/")
}
}
impl std::fmt::Display for InnerProjectPathUnix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<RawProjectPath> for InnerProjectPathUnix {
fn from(value: RawProjectPath) -> Self {
// Convert windows path to unix path.
// .mrpacks no longer generate windows paths, but this is here for backwards compatibility before this was fixed
// https://github.com/modrinth/theseus/issues/595
InnerProjectPathUnix(value.0.replace('\\', "/"))
}
}
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
struct RawProjectPath(pub String);
impl From<InnerProjectPathUnix> for RawProjectPath {
fn from(value: InnerProjectPathUnix) -> Self {
RawProjectPath(value.0)
}
}
/// newtype wrapper over a Profile path, to be usable as a clear identifier for the kind of path used
/// eg: for "a/b/c/profiles/My Mod/mods/myproj", the ProjectPathId would be "mods/myproj"
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Hash)]
@@ -102,11 +141,11 @@ impl std::fmt::Display for ProfilePathId {
pub struct ProjectPathId(pub PathBuf);
impl ProjectPathId {
// Create a new ProjectPathId from a full file path
pub async fn from_fs_path(path: PathBuf) -> crate::Result<Self> {
let path: PathBuf = io::canonicalize(path)?;
pub async fn from_fs_path(path: &PathBuf) -> crate::Result<Self> {
let profiles_dir: PathBuf = io::canonicalize(
State::get().await?.directories.profiles_dir().await,
)?;
let path: PathBuf = io::canonicalize(path)?;
let path = path
.strip_prefix(profiles_dir)
.ok()
@@ -131,13 +170,14 @@ impl ProjectPathId {
// Gets inner path in unix convention as a String
// ie: 'mods\myproj' -> 'mods/myproj'
// Used for exporting to mrpack, which should have a singular convention
pub fn get_inner_path_unix(&self) -> crate::Result<String> {
Ok(self
.0
.components()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>()
.join("/"))
pub fn get_inner_path_unix(&self) -> InnerProjectPathUnix {
InnerProjectPathUnix(
self.0
.components()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect::<Vec<_>>()
.join("/"),
)
}
// Create a new ProjectPathId from a relative path
@@ -849,8 +889,6 @@ impl Profiles {
// Fetch online from Modrinth each latest version
future::try_join_all(modrinth_updatables.into_iter().map(
|(profile_path, linked_project)| {
let profile_path = profile_path;
let linked_project = linked_project;
let state = state.clone();
async move {
let creds = state.credentials.read().await;

View File

@@ -815,7 +815,7 @@ pub async fn infer_data_from_files(
let mut corrected_hashmap = HashMap::new();
let mut stream = tokio_stream::iter(return_projects);
while let Some((h, v)) = stream.next().await {
let h = ProjectPathId::from_fs_path(h).await?;
let h = ProjectPathId::from_fs_path(&h).await?;
corrected_hashmap.insert(h, v);
}

View File

@@ -43,7 +43,9 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
r"C:\Program Files (x86)\Eclipse Adoptium",
];
for java_path in java_paths {
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {
continue;
};
for java_subpath in java_subpaths.flatten() {
let path = java_subpath.path();
jre_paths.insert(path.join("bin"));
@@ -97,7 +99,7 @@ pub fn get_paths_from_jre_winregkey(jre_key: RegKey) -> HashSet<PathBuf> {
for subkey_value in subkey_value_names {
let path: Result<String, std::io::Error> =
subkey.get_value(subkey_value);
let Ok(path) = path else {continue};
let Ok(path) = path else { continue };
jre_paths.insert(PathBuf::from(path).join("bin"));
}
@@ -264,7 +266,9 @@ pub async fn check_java_at_filepaths(
pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
// Attempt to canonicalize the potential java filepath
// If it fails, this path does not exist and None is returned (no Java here)
let Ok(path) = io::canonicalize(path) else { return None };
let Ok(path) = io::canonicalize(path) else {
return None;
};
// Checks for existence of Java at this filepath
// Adds JAVA_BIN to the end of the path if it is not already there

View File

@@ -56,7 +56,12 @@ pub const ARCH_WIDTH: &str = "64";
pub const ARCH_WIDTH: &str = "32";
// Platform rule handling
pub fn os_rule(rule: &OsRule, java_arch: &str) -> bool {
pub fn os_rule(
rule: &OsRule,
java_arch: &str,
// Minecraft updated over 1.18.2 (supports MacOS Natively)
minecraft_updated: bool,
) -> bool {
let mut rule_match = true;
if let Some(ref arch) = rule.arch {
@@ -64,8 +69,14 @@ pub fn os_rule(rule: &OsRule, java_arch: &str) -> bool {
}
if let Some(name) = &rule.name {
rule_match &=
&Os::native() == name || &Os::native_arch(java_arch) == name;
if minecraft_updated
&& (name != &Os::LinuxArm64 || name != &Os::LinuxArm32)
{
rule_match &=
&Os::native() == name || &Os::native_arch(java_arch) == name;
} else {
rule_match &= &Os::native_arch(java_arch) == name;
}
}
if let Some(version) = &rule.version {

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus_cli"
version = "0.5.4"
version = "0.6.0"
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2018"

View File

@@ -1,7 +1,7 @@
{
"name": "theseus_gui",
"private": true,
"version": "0.5.4",
"version": "0.6.0",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus_gui"
version = "0.5.4"
version = "0.6.0"
description = "A Tauri App"
authors = ["you"]
license = ""

View File

@@ -3,7 +3,7 @@ use daedalus::modded::LoaderVersion;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use theseus::prelude::*;
use theseus::{prelude::*, InnerProjectPathUnix};
use uuid::Uuid;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
@@ -32,7 +32,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
profile_edit,
profile_edit_icon,
profile_export_mrpack,
profile_get_potential_override_folders,
profile_get_pack_export_candidates,
])
.build()
}
@@ -117,8 +117,8 @@ pub async fn profile_check_installed(
/// Installs/Repairs a profile
/// invoke('plugin:profile|profile_install')
#[tauri::command]
pub async fn profile_install(path: ProfilePathId) -> Result<()> {
profile::install(&path).await?;
pub async fn profile_install(path: ProfilePathId, force: bool) -> Result<()> {
profile::install(&path, force).await?;
Ok(())
}
@@ -228,20 +228,13 @@ pub async fn profile_export_mrpack(
Ok(())
}
// Given a folder path, populate a Vec of all the subfolders
// Intended to be used for finding potential override folders
// profile
// -- folder1
// -- folder2
// -- file1
// => [folder1, folder2]
/// See [`profile::get_pack_export_candidates`]
#[tauri::command]
pub async fn profile_get_potential_override_folders(
pub async fn profile_get_pack_export_candidates(
profile_path: ProfilePathId,
) -> Result<Vec<PathBuf>> {
let overrides =
profile::get_potential_override_folders(profile_path).await?;
Ok(overrides)
) -> Result<Vec<InnerProjectPathUnix>> {
let candidates = profile::get_pack_export_candidates(&profile_path).await?;
Ok(candidates)
}
// Run minecraft using a profile using the default credentials

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "Modrinth App",
"version": "0.5.4"
"version": "0.6.0"
},
"tauri": {
"allowlist": {

View File

@@ -1,20 +1,22 @@
<svg
data-v-8c2610d6=""
xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
viewBox="0 0 100 100"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2;"><circle cx="50" cy="50" r="50" style="fill:#fff;"
/>
<g transform="translate(14.39 14.302) scale(.09916)"><clipPath id="a"><path d="M0 0h705.6v720H0z"/></clipPath>
<g clip-path="url(#a)"><path d="M-4117.16-2597.44v139.42h193.74c-8.51 44.84-34.04 82.8-72.33 108.33l116.84 90.66c68.07-62.84 107.35-155.13 107.35-264.77 0-25.53-2.29-50.07-6.55-73.63l-339.05-.01Z" style="fill:#4285f4;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="m-4318.92-2463.46-26.35 20.17-93.28 72.65c59.24 117.49 180.65 198.66 321.38 198.66 97.2 0 178.69-32.07 238.25-87.05l-116.83-90.66c-32.08 21.6-72.99 34.69-121.42 34.69-93.6 0-173.13-63.16-201.6-148.25l-.15-.21Z"
style="fill:#34a853;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="M-4438.55-2693.33c-24.54 48.44-38.61 103.09-38.61 161.34 0 58.26 14.07 112.91 38.61 161.35 0 .32 119.79-92.95 119.79-92.95-7.2-21.6-11.46-44.5-11.46-68.4 0-23.89 4.26-46.8 11.46-68.4l-119.79-92.94Z"
style="fill:#fbbc05;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="M-4117.16-2748.64c53.02 0 100.14 18.33 137.78 53.67l103.09-103.09c-62.51-58.25-143.67-93.93-240.87-93.93-140.73 0-262.15 80.84-321.39 198.66l119.79 92.95c28.47-85.09 108-148.26 201.6-148.26Z"
style="fill:#ea4335;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
</g>
<g transform="translate(14.39 14.302) scale(.09916)">
<path
d="M-4117.16-2597.44v139.42h193.74c-8.51 44.84-34.04 82.8-72.33 108.33l116.84 90.66c68.07-62.84 107.35-155.13 107.35-264.77 0-25.53-2.29-50.07-6.55-73.63l-339.05-.01Z"
style="fill:#4285f4;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="m-4318.92-2463.46-26.35 20.17-93.28 72.65c59.24 117.49 180.65 198.66 321.38 198.66 97.2 0 178.69-32.07 238.25-87.05l-116.83-90.66c-32.08 21.6-72.99 34.69-121.42 34.69-93.6 0-173.13-63.16-201.6-148.25l-.15-.21Z"
style="fill:#34a853;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="M-4438.55-2693.33c-24.54 48.44-38.61 103.09-38.61 161.34 0 58.26 14.07 112.91 38.61 161.35 0 .32 119.79-92.95 119.79-92.95-7.2-21.6-11.46-44.5-11.46-68.4 0-23.89 4.26-46.8 11.46-68.4l-119.79-92.94Z"
style="fill:#fbbc05;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
<path
d="M-4117.16-2748.64c53.02 0 100.14 18.33 137.78 53.67l103.09-103.09c-62.51-58.25-143.67-93.93-240.87-93.93-140.73 0-262.15 80.84-321.39 198.66l119.79 92.95c28.47-85.09 108-148.26 201.6-148.26Z"
style="fill:#ea4335;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -2,10 +2,9 @@
import { Button, Checkbox, Modal, XIcon, PlusIcon } from 'omorphia'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import { ref } from 'vue'
import { export_profile_mrpack, get_potential_override_folders } from '@/helpers/profile.js'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { open } from '@tauri-apps/api/dialog'
import { handleError } from '@/store/notifications.js'
import { sep } from '@tauri-apps/api/path'
import { useTheming } from '@/store/theme'
const props = defineProps({
@@ -34,8 +33,9 @@ const themeStore = useTheming()
const initFiles = async () => {
const newFolders = new Map()
const sep = '/'
files.value = []
await get_potential_override_folders(props.instance.path).then((filePaths) =>
await get_pack_export_candidates(props.instance.path).then((filePaths) =>
filePaths
.map((folder) => ({
path: folder,

View File

@@ -187,7 +187,7 @@ onUnmounted(() => unlisten())
<div class="instance">
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
<Avatar
size="sm"
size="lg"
:src="
props.instance.metadata
? !props.instance.metadata.icon ||

View File

@@ -20,7 +20,13 @@
</div>
<div class="input-row">
<p class="input-label">Name</p>
<input v-model="profile_name" autocomplete="off" class="text-input" type="text" />
<input
v-model="profile_name"
autocomplete="off"
class="text-input"
type="text"
maxlength="100"
/>
</div>
<div class="input-row">
<p class="input-label">Loader</p>

View File

@@ -247,13 +247,15 @@ const check_valid = computed(() => {
</Button>
<div
v-tooltip="
profile.metadata.linked_data && !profile.installedMod
? 'Unpair an instance to add mods.'
profile.metadata.linked_data?.locked && !profile.installedMod
? 'Unpair or unlock an instance to add mods.'
: ''
"
>
<Button
:disabled="profile.installedMod || profile.installing || profile.metadata.linked_data"
:disabled="
profile.installedMod || profile.installing || profile.metadata.linked_data?.locked
"
@click="install(profile)"
>
<DownloadIcon v-if="!profile.installedMod && !profile.installing" />
@@ -263,7 +265,7 @@ const check_valid = computed(() => {
? 'Installing...'
: profile.installedMod
? 'Installed'
: profile.metadata.linked_data
: profile.metadata.linked_data && profile.metadata.linked_data.locked
? 'Paired'
: 'Install'
}}

View File

@@ -72,8 +72,8 @@ export async function check_installed(path, projectId) {
}
// Installs/Repairs a profile
export async function install(path) {
return await invoke('plugin:profile|profile_install', { path })
export async function install(path, force) {
return await invoke('plugin:profile|profile_install', { path, force })
}
// Updates all of a profile's projects
@@ -151,8 +151,8 @@ export async function export_profile_mrpack(
// -- file1
// => [mods, resourcepacks]
// allows selection for 'included_overrides' in export_profile_mrpack
export async function get_potential_override_folders(profilePath) {
return await invoke('plugin:profile|profile_get_potential_override_folders', { profilePath })
export async function get_pack_export_candidates(profilePath) {
return await invoke('plugin:profile|profile_get_pack_export_candidates', { profilePath })
}
// Run Minecraft using a pathed profile

View File

@@ -549,7 +549,11 @@ onUnmounted(() => unlistenOffline())
size="sm"
/>
<div class="small-instance_info">
<span class="title">{{ instanceContext.metadata.name }}</span>
<span class="title">{{
instanceContext.metadata.name.length > 20
? instanceContext.metadata.name.substring(0, 20) + '...'
: instanceContext.metadata.name
}}</span>
<span>
{{
instanceContext.metadata.loader.charAt(0).toUpperCase() +

View File

@@ -26,15 +26,19 @@ const pageOptions = ['Home', 'Library']
const themeStore = useTheming()
const fetchSettings = await get().catch(handleError)
const accessSettings = async () => {
const settings = await get()
if (!fetchSettings.java_globals.JAVA_8)
fetchSettings.java_globals.JAVA_8 = { path: '', version: '' }
if (!fetchSettings.java_globals.JAVA_17)
fetchSettings.java_globals.JAVA_17 = { path: '', version: '' }
if (!settings.java_globals.JAVA_8) settings.java_globals.JAVA_8 = { path: '', version: '' }
if (!settings.java_globals.JAVA_17) settings.java_globals.JAVA_17 = { path: '', version: '' }
fetchSettings.javaArgs = fetchSettings.custom_java_args.join(' ')
fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ')
settings.javaArgs = settings.custom_java_args.join(' ')
settings.envArgs = settings.custom_env_args.map((x) => x.join('=')).join(' ')
return settings
}
const fetchSettings = await accessSettings().catch(handleError)
const settings = ref(fetchSettings)
const settingsDir = ref(settings.value.loaded_config_dir)
@@ -43,6 +47,10 @@ const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1
watch(
settings,
async (oldSettings, newSettings) => {
if (oldSettings.loaded_config_dir !== newSettings.loaded_config_dir) {
return
}
const setSettings = JSON.parse(JSON.stringify(newSettings))
if (setSettings.opt_out_analytics) {
@@ -117,6 +125,8 @@ async function findLauncherDir() {
async function refreshDir() {
await change_config_dir(settingsDir.value)
settings.value = await accessSettings().catch(handleError)
settingsDir.value = settings.value.loaded_config_dir
}
</script>
@@ -385,7 +395,7 @@ async function refreshDir() {
v-model="settings.memory.maximum"
:min="8"
:max="maxMemory"
:step="1"
:step="64"
unit="mb"
/>
</div>

View File

@@ -161,7 +161,13 @@ const breadcrumbs = useBreadcrumbs()
const instance = ref(await get(route.params.id).catch(handleError))
breadcrumbs.setName('Instance', instance.value.metadata.name)
breadcrumbs.setName(
'Instance',
instance.value.metadata.name.length > 40
? instance.value.metadata.name.substring(0, 40) + '...'
: instance.value.metadata.name
)
breadcrumbs.setContext({
name: instance.value.metadata.name,
link: route.path,
@@ -203,7 +209,7 @@ const checkProcess = async () => {
// Get information on associated modrinth versions, if any
const modrinthVersions = ref([])
if (!(await isOffline()) && instance.value.metadata.linked_data) {
if (!(await isOffline()) && instance.value.metadata.linked_data?.project_id) {
modrinthVersions.value = await useFetch(
`https://api.modrinth.com/v2/project/${instance.value.metadata.linked_data.project_id}/version`,
'project'
@@ -366,6 +372,8 @@ Button {
.name {
font-size: 1.25rem;
color: var(--color-contrast);
overflow: hidden;
text-overflow: ellipsis;
}
.metadata {

View File

@@ -524,13 +524,26 @@ onUnmounted(() => {
display: flex;
padding: 0.6rem;
flex-direction: row;
overflow: auto;
gap: 0.5rem;
&::-webkit-scrollbar-track,
&::-webkit-scrollbar-thumb {
border-radius: 10px;
}
}
:deep(.vue-recycle-scroller__item-wrapper) {
overflow: visible; /* Enables horizontal scrolling */
}
:deep(.vue-recycle-scroller) {
&::-webkit-scrollbar-corner {
background-color: var(--color-bg);
border-radius: 0 0 10px 0;
}
}
.scroller {
height: 100%;
}

View File

@@ -243,7 +243,7 @@
:disabled="!overrideMemorySettings"
:min="8"
:max="maxMemory"
:step="1"
:step="64"
unit="mb"
/>
</div>
@@ -362,7 +362,10 @@
<span class="label__description">
<strong>Version: </strong>
{{
installedVersionData.name.charAt(0).toUpperCase() + installedVersionData.name.slice(1)
installedVersionData?.name != null
? installedVersionData.name.charAt(0).toUpperCase() +
installedVersionData.name.slice(1)
: getLocalVersion(props.instance.path)
}}
</span>
</label>
@@ -401,7 +404,7 @@
</Button>
</div>
<div class="adjacent-input">
<div v-if="props.instance.metadata.linked_data.project_id" class="adjacent-input">
<label for="change-modpack-version">
<span class="label__title">Change modpack version</span>
<span class="label__description">
@@ -465,7 +468,7 @@
id="repair-profile"
color="highlight"
:disabled="installing || inProgress || repairing || offline"
@click="repairProfile"
@click="repairProfile(true)"
>
<HammerIcon /> Repair
</Button>
@@ -576,9 +579,11 @@ const modpackVersionModal = ref(null)
const instancesList = Object.values(await list(true))
const availableGroups = ref([
...instancesList.reduce((acc, obj) => {
return acc.concat(obj.metadata.groups)
}, []),
...new Set(
instancesList.reduce((acc, obj) => {
return acc.concat(obj.metadata.groups)
}, [])
),
])
async function resetIcon() {
@@ -641,9 +646,10 @@ 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)
)
const installedVersionData = computed(() => {
if (!installedVersion.value) return null
return props.versions.find((version) => version.id === installedVersion.value)
})
watch(
[
@@ -671,6 +677,15 @@ watch(
{ deep: true }
)
const getLocalVersion = (path) => {
const pathSlice = path.split(' ').slice(-1).toString()
// If the path ends in (1), (2), etc. it's a duplicate instance and no version can be obtained.
if (/^\(\d\)/.test(pathSlice)) {
return 'Unknown'
}
return pathSlice
}
const editProfileObject = computed(() => {
const editProfile = {
metadata: {
@@ -740,9 +755,9 @@ async function duplicateProfile() {
})
}
async function repairProfile() {
async function repairProfile(force) {
repairing.value = true
await install(props.instance.path).catch(handleError)
await install(props.instance.path, force).catch(handleError)
repairing.value = false
mixpanel_track('InstanceRepair', {
@@ -895,7 +910,7 @@ async function saveGvLoaderEdits() {
editProfile.metadata.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value]
}
await edit(props.instance.path, editProfile).catch(handleError)
await repairProfile()
await repairProfile(false)
editing.value = false
changeVersionsModal.value.hide()

View File

@@ -14,7 +14,11 @@
size="sm"
/>
<div class="small-instance_info">
<span class="title">{{ instance.metadata.name }}</span>
<span class="title">{{
instance.metadata.name.length > 20
? instance.metadata.name.substring(0, 20) + '...'
: instance.metadata.name
}}</span>
<span>
{{
instance.metadata.loader.charAt(0).toUpperCase() + instance.metadata.loader.slice(1)