fmt clippy prettier

This commit is contained in:
Wyatt Verchere 2024-01-30 19:39:46 -08:00
parent 719aded698
commit f9beea8ef2
22 changed files with 616 additions and 385 deletions

View File

@ -14,8 +14,8 @@ use crate::{
/// (Does not include modrinth://)
pub async fn handle_url(sublink: &str) -> crate::Result<CommandPayload> {
Ok(match sublink.split_once('/') {
Some(("shared_profile", link)) => {
CommandPayload::OpenSharedProfile { link: link.to_string() }
Some(("shared_profile", link)) => CommandPayload::OpenSharedProfile {
link: link.to_string(),
},
// /mod/{id} - Installs a mod of mod id
Some(("mod", id)) => CommandPayload::InstallMod { id: id.to_string() },

View File

@ -10,8 +10,8 @@ pub mod pack;
pub mod process;
pub mod profile;
pub mod safety;
pub mod shared_profile;
pub mod settings;
pub mod shared_profile;
pub mod tags;
pub mod data {
@ -30,7 +30,7 @@ pub mod prelude {
event::CommandPayload,
jre, metadata, pack, process,
profile::{self, create, Profile},
settings,
settings, shared_profile,
state::JavaGlobals,
state::{Dependency, ProfilePathId, ProjectPathId},
util::{
@ -38,6 +38,5 @@ pub mod prelude {
jre::JavaVersion,
},
State,
shared_profile,
};
}

View File

@ -391,16 +391,16 @@ pub async fn set_profile_information(
let project_id = description.project_id.clone();
let version_id = description.version_id.clone();
prof.metadata.linked_data = if project_id.is_some()
&& version_id.is_some()
{
prof.metadata.linked_data =
if project_id.is_some() && version_id.is_some() {
Some(LinkedData::ModrinthModpack {
project_id,
version_id,
locked: if !ignore_lock {
Some(true)
} else {
prof.metadata.linked_data.as_ref().and_then(|x| if let LinkedData::ModrinthModpack {
prof.metadata.linked_data.as_ref().and_then(|x| {
if let LinkedData::ModrinthModpack {
locked: Some(locked),
..
} = x
@ -408,6 +408,7 @@ pub async fn set_profile_information(
Some(*locked)
} else {
None
}
})
},
})

View File

@ -102,11 +102,14 @@ pub async fn profile_create(
}
profile.metadata.linked_data = linked_data;
if let Some(LinkedData::ModrinthModpack{ project_id, version_id, locked, .. }) = &mut profile.metadata.linked_data {
*locked = Some(
project_id.is_some()
&& version_id.is_some(),
);
if let Some(LinkedData::ModrinthModpack {
project_id,
version_id,
locked,
..
}) = &mut profile.metadata.linked_data
{
*locked = Some(project_id.is_some() && version_id.is_some());
}
emit_profile(

View File

@ -3,12 +3,12 @@
use crate::event::emit::{
emit_loading, init_loading, loading_try_for_each_concurrent,
};
use crate::state::LinkedData;
use crate::event::LoadingBarType;
use crate::pack::install_from::{
EnvType, PackDependency, PackFile, PackFileHash, PackFormat,
};
use crate::prelude::{JavaVersion, ProfilePathId, ProjectPathId};
use crate::state::LinkedData;
use crate::state::{InnerProjectPathUnix, ProjectMetadata, SideType};
use crate::util::fetch;
@ -116,15 +116,14 @@ pub async fn get_mod_full_path(
project_path: &ProjectPathId,
) -> crate::Result<PathBuf> {
if get(profile_path, Some(true)).await?.is_some() {
let full_path = io::canonicalize(
project_path.get_full_path(&profile_path).await?,
)?;
let full_path =
io::canonicalize(project_path.get_full_path(profile_path).await?)?;
return Ok(full_path);
}
Err(crate::ErrorKind::OtherError(format!(
"Tried to get the full path of a nonexistent or unloaded project at path {}!",
project_path.get_full_path(&profile_path).await?.display()
project_path.get_full_path(profile_path).await?.display()
))
.into())
}
@ -873,13 +872,13 @@ pub async fn try_update_playtime(path: &ProfilePathId) -> crate::Result<()> {
let res = if updated_recent_playtime > 0 {
// Create update struct to send to Labrinth
let modrinth_pack_version_id =
profile.metadata.linked_data.and_then(|l|
if let LinkedData::ModrinthModpack {
version_id,
..
} = l {
profile.metadata.linked_data.and_then(|l| {
if let LinkedData::ModrinthModpack { version_id, .. } = l {
Some(version_id)
} else { None });
} else {
None
}
});
let playtime_update_json = json!({
"seconds": updated_recent_playtime,
"loader": profile.metadata.loader.to_string(),

View File

@ -6,7 +6,7 @@ use crate::{
pack::{self, install_from::generate_pack_from_version_id},
prelude::{ProfilePathId, ProjectPathId},
profile::get,
state::{ProfileInstallStage, Project, LinkedData},
state::{LinkedData, ProfileInstallStage, Project},
LoadingBarType, State,
};
use futures::try_join;
@ -34,7 +34,8 @@ pub async fn update_managed_modrinth_version(
project_id: Some(ref project_id),
version_id: Some(ref version_id),
..
}) = profile.metadata.linked_data else {
}) = profile.metadata.linked_data
else {
return Err(unmanaged_err().into());
};
@ -109,7 +110,8 @@ pub async fn repair_managed_modrinth(
project_id: Some(ref project_id),
version_id: Some(ref version_id),
..
}) = profile.metadata.linked_data else {
}) = profile.metadata.linked_data
else {
return Err(unmanaged_err().into());
};

View File

@ -1,6 +1,6 @@
//! Theseus profile management interface
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use tokio::fs;
use io::IOError;
@ -10,7 +10,7 @@ use crate::{
event::emit::{emit_loading, init_loading},
prelude::DirectoryInfo,
state::{self, Profiles},
util::{io, fetch},
util::{fetch, io},
};
pub use crate::{
state::{
@ -109,7 +109,6 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
let old_config_dir =
state_write.directories.config_dir.read().await.clone();
// Reset file watcher
tracing::trace!("Reset file watcher");
let file_watcher = state::init_watcher().await?;
@ -125,13 +124,17 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
.await
.map_err(|e| IOError::with_path(e, &old_config_dir))?
{
let entry_path = entry.path();
if let Some(file_name) = entry_path.file_name() {
// We are only moving the profiles and metadata folders
if file_name == state::PROFILES_FOLDER_NAME || file_name == state::METADATA_FOLDER_NAME {
if file_name == state::PROFILES_FOLDER_NAME
|| file_name == state::METADATA_FOLDER_NAME
{
if across_drives {
entries.extend(crate::pack::import::get_all_subfiles(&entry_path).await?);
entries.extend(
crate::pack::import::get_all_subfiles(&entry_path)
.await?,
);
deletable_entries.push(entry_path.clone());
} else {
entries.push(entry_path.clone());
@ -151,8 +154,7 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
} else {
io::rename(entry_path.clone(), new_path.clone()).await?;
}
emit_loading(&loading_bar, 80.0 * (1.0 / num_entries), None)
.await?;
emit_loading(&loading_bar, 80.0 * (1.0 / num_entries), None).await?;
}
tracing::trace!("Setting configuration setting");
@ -199,7 +201,8 @@ pub async fn set_config_dir(new_config_dir: PathBuf) -> crate::Result<()> {
&loading_bar,
10.0 * (1.0 / deletable_entries_len as f64),
None,
).await?;
)
.await?;
}
// Reset file watcher
@ -228,7 +231,6 @@ fn is_different_drive(path1: &Path, path2: &Path) -> bool {
root1 != root2
}
pub async fn is_dir_writeable(new_config_dir: PathBuf) -> crate::Result<bool> {
let temp_path = new_config_dir.join(".tmp");
match fs::write(temp_path.clone(), "test").await {

View File

@ -1,9 +1,20 @@
use std::path::PathBuf;
use crate::{
config::MODRINTH_API_URL_INTERNAL,
prelude::{
LinkedData, ModLoader, ProfilePathId, ProjectMetadata, ProjectPathId,
},
profile,
state::{Profile, Profiles},
util::{
fetch::{fetch_advanced, REQWEST_CLIENT},
io,
},
};
use chrono::{DateTime, Utc};
use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::{config::MODRINTH_API_URL_INTERNAL, prelude::{LinkedData, ModLoader, ProfilePathId, ProjectMetadata, ProjectPathId}, profile, util::{fetch::{fetch_advanced, REQWEST_CLIENT}, io}, state::{Profile, Profiles}};
#[derive(Deserialize, Serialize, Debug)]
pub struct SharedProfile {
@ -23,7 +34,7 @@ pub struct SharedProfile {
pub overrides: Vec<SharedProfileOverride>,
pub share_links: Option<Vec<SharedProfileLink>>,
pub users: Option<Vec<String>>
pub users: Option<Vec<String>>,
}
#[derive(Deserialize, Serialize, Debug)]
@ -49,23 +60,33 @@ pub struct SharedProfileOverrideHashes {
// Create a new shared profile from ProfilePathId
// This converts the LinkedData to a SharedProfile and uploads it to the Labrinth API
#[tracing::instrument]
pub async fn create(
profile_id: ProfilePathId,
) -> crate::Result<()> {
pub async fn create(profile_id: ProfilePathId) -> crate::Result<()> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
// Currently existing linked data should fail
match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { .. }) => {
return Err(crate::ErrorKind::OtherError("Profile already linked to a shared profile".to_string()).as_error());
},
return Err(crate::ErrorKind::OtherError(
"Profile already linked to a shared profile".to_string(),
)
.as_error());
}
Some(LinkedData::ModrinthModpack { .. }) => {
return Err(crate::ErrorKind::OtherError("Profile already linked to a modrinth project".to_string()).as_error());
},
return Err(crate::ErrorKind::OtherError(
"Profile already linked to a modrinth project".to_string(),
)
.as_error());
}
None => {}
};
@ -74,25 +95,36 @@ pub async fn create(
let loader_version = profile.metadata.loader_version;
let game_version = profile.metadata.game_version;
let modrinth_projects : Vec<_> = profile.projects.iter()
.filter_map(|(_, project)|if let ProjectMetadata::Modrinth { ref version, .. } = project.metadata {
let modrinth_projects: Vec<_> = profile
.projects
.iter()
.filter_map(|(_, project)| {
if let ProjectMetadata::Modrinth { ref version, .. } =
project.metadata
{
Some(&version.id)
} else {
None
}).collect();
}
})
.collect();
let override_files : Vec<_> = profile.projects.iter()
.filter_map(|(id, project)|if let ProjectMetadata::Inferred { ..} = project.metadata {
let override_files: Vec<_> = profile
.projects
.iter()
.filter_map(|(id, project)| {
if let ProjectMetadata::Inferred { .. } = project.metadata {
Some(id)
} else {
None
}).collect();
}
})
.collect();
// Create the profile on the Labrinth API
let response = REQWEST_CLIENT
.post(
format!("{MODRINTH_API_URL_INTERNAL}client/profile"),
).header("Authorization", &creds.session)
.post(format!("{MODRINTH_API_URL_INTERNAL}client/profile"))
.header("Authorization", &creds.session)
.json(&serde_json::json!({
"name": name,
"loader": loader.as_api_str(),
@ -101,12 +133,20 @@ pub async fn create(
"game_version": game_version,
"versions": modrinth_projects,
}))
.send().await?;
.send()
.await?;
let profile_response = response.json::<serde_json::Value>().await?;
// Extract the profile ID from the response
let shared_profile_id = profile_response["id"].as_str().ok_or_else(|| crate::ErrorKind::OtherError("Could not parse response from Labrinth API".to_string()))?.to_string();
let shared_profile_id = profile_response["id"]
.as_str()
.ok_or_else(|| {
crate::ErrorKind::OtherError(
"Could not parse response from Labrinth API".to_string(),
)
})?
.to_string();
// Unmanaged projects
let mut data = vec![]; // 'data' field, giving installation context to labrinth
@ -114,26 +154,39 @@ pub async fn create(
for override_file in override_files {
let path = override_file.get_inner_path_unix();
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else { continue };
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else {
continue;
};
// Load override to file
let full_path = &override_file.get_full_path(&profile_id).await?;
let file_bytes = io::read(full_path).await?;
let ext = full_path.extension().and_then(|x| x.to_str()).unwrap_or_default();
let mime = project_file_type(ext).ok_or_else(|| crate::ErrorKind::OtherError(format!("Could not determine file type for {}", ext)))?;
let ext = full_path
.extension()
.and_then(|x| x.to_str())
.unwrap_or_default();
let mime = project_file_type(ext).ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Could not determine file type for {}",
ext
))
})?;
data.push(serde_json::json!({
"file_name": name.clone(),
"install_path": path
}));
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(name.clone()).mime_str(mime)?;
let part = reqwest::multipart::Part::bytes(file_bytes)
.file_name(name.clone())
.mime_str(mime)?;
parts.push((name.clone(), part));
}
// Build multipart with 'data' field first
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
let json_part = reqwest::multipart::Part::text(serde_json::to_string(&data)?);//mime_str("application/json")?;
let json_part =
reqwest::multipart::Part::text(serde_json::to_string(&data)?); //mime_str("application/json")?;
multipart = multipart.part("data", json_part);
for (name, part) in parts {
multipart = multipart.part(name, part);
@ -154,7 +207,8 @@ pub async fn create(
is_owner: true,
});
async { Ok(()) }
}).await?;
})
.await?;
// Sync
crate::State::sync().await?;
@ -175,7 +229,10 @@ pub fn project_file_type(ext: &str) -> Option<&str> {
pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
let state = crate::State::get().await?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
// First, get list of shared profiles the user has access to
#[derive(Deserialize, Serialize, Debug)]
@ -199,11 +256,11 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
}
let response = REQWEST_CLIENT
.get(
format!("{MODRINTH_API_URL_INTERNAL}client/user"),
)
.get(format!("{MODRINTH_API_URL_INTERNAL}client/user"))
.header("Authorization", &creds.session)
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
let profiles = response.json::<Vec<SharedProfileResponse>>().await?;
@ -223,18 +280,28 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
let id = profile.id;
let response = REQWEST_CLIENT
.get(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{id}/files"),
)
.get(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/{id}/files"
))
.header("Authorization", &creds.session)
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
let files = response.json::<SharedFiles>().await?;
shared_profiles.push(SharedProfile {
id,
name: profile.name,
is_owned: profile.owner_id == state.credentials.read().await.0.as_ref().map(|x| x.user.id.as_str()).unwrap_or_default(),
is_owned: profile.owner_id
== state
.credentials
.read()
.await
.0
.as_ref()
.map(|x| x.user.id.as_str())
.unwrap_or_default(),
owner_id: profile.owner_id,
loader: profile.loader,
loader_version: profile.loader_version,
@ -253,7 +320,9 @@ pub async fn get_all() -> crate::Result<Vec<SharedProfile>> {
}
#[tracing::instrument]
pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePathId> {
pub async fn install(
shared_profile: SharedProfile,
) -> crate::Result<ProfilePathId> {
let state = crate::State::get().await?;
let linked_data = LinkedData::SharedProfile {
@ -272,10 +341,14 @@ pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePat
Some(linked_data),
None,
None,
).await?;
)
.await?;
// Get the profile
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
// TODO: concurrent requests
@ -297,14 +370,14 @@ pub async fn install(shared_profile : SharedProfile) -> crate::Result<ProfilePat
)
.await?;
profile.add_project_bytes_directly(&file_override.install_path, file).await?;
profile
.add_project_bytes_directly(&file_override.install_path, file)
.await?;
}
Ok(profile_id)
}
// Structure repesenting a synchronization difference between a local profile and a shared profile
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct SharedModpackFileUpdate {
@ -321,32 +394,50 @@ pub struct SharedModpackFileUpdate {
}
#[tracing::instrument]
pub async fn check_updated(profile_id: &ProfilePathId, shared_profile : &SharedProfile) -> crate::Result<SharedModpackFileUpdate> {
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
pub async fn check_updated(
profile_id: &ProfilePathId,
shared_profile: &SharedProfile,
) -> crate::Result<SharedModpackFileUpdate> {
let profile: Profile =
profile::get(profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
// Check if the metadata is the same- if different, we return false with no file updates
if profile.metadata.name != shared_profile.name ||
profile.metadata.loader != shared_profile.loader ||
profile.metadata.loader_version.map(|x| x.id).unwrap_or_default() != shared_profile.loader_version ||
profile.metadata.game_version != shared_profile.game_version {
if profile.metadata.name != shared_profile.name
|| profile.metadata.loader != shared_profile.loader
|| profile
.metadata
.loader_version
.map(|x| x.id)
.unwrap_or_default()
!= shared_profile.loader_version
|| profile.metadata.game_version != shared_profile.game_version
{
return Ok(SharedModpackFileUpdate::default());
}
// Check if the projects are the same- we check each override by hash and each modrinth project by version id
let mut modrinth_projects = shared_profile.versions.clone();
let mut overrides = shared_profile.overrides.clone();
let unsynced_projects : Vec<_> = profile.projects.into_iter().filter_map(|(id, project)|{
let unsynced_projects: Vec<_> = profile
.projects
.into_iter()
.filter_map(|(id, project)| {
match project.metadata {
ProjectMetadata::Modrinth { ref version, .. } => {
if modrinth_projects.contains(&version.id) {
modrinth_projects.retain(|x| x != &version.id);
}
else {
} else {
return Some(id);
}
},
}
ProjectMetadata::Inferred { .. } => {
let Some(matching_override) = overrides.iter().position(|o| o.install_path.to_string_lossy().to_string() == id.get_inner_path_unix().0)
let Some(matching_override) =
overrides.iter().position(|o| {
o.install_path.to_string_lossy()
== id.get_inner_path_unix().0
})
else {
return Some(id);
};
@ -362,40 +453,56 @@ pub async fn check_updated(profile_id: &ProfilePathId, shared_profile : &SharedP
}
ProjectMetadata::Unknown => {
// TODO: What to do for unknown projects?
return Some(id)
return Some(id);
}
}
None
}).collect();
})
.collect();
Ok(SharedModpackFileUpdate {
is_synced: modrinth_projects.is_empty() && overrides.is_empty() && unsynced_projects.is_empty(),
is_synced: modrinth_projects.is_empty()
&& overrides.is_empty()
&& unsynced_projects.is_empty(),
unsynced_projects,
missing_versions: modrinth_projects,
missing_overrides: overrides,
})
}
// Updates projects for a given ProfilePathId from a SharedProfile
// This updates the local profile to match the shared profile on the Labrinth API
#[tracing::instrument]
pub async fn inbound_sync(
profile_id: ProfilePathId,
) -> crate::Result<()> {
pub async fn inbound_sync(profile_id: ProfilePathId) -> crate::Result<()> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
// Get linked
let shared_profile = match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { ref profile_id, .. }) => profile_id,
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
_ => {
return Err(crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
.as_error())
}
};
// Get updated shared profile
let shared_profile = get_all().await?.into_iter().find(|x| &x.id == shared_profile).ok_or_else(|| crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()))?;
let shared_profile = get_all()
.await?
.into_iter()
.find(|x| &x.id == shared_profile)
.ok_or_else(|| {
crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
})?;
let update_data = check_updated(&profile_id, &shared_profile).await?;
if update_data.is_synced {
@ -426,7 +533,9 @@ pub async fn inbound_sync(
)
.await?;
profile.add_project_bytes_directly(&file_override.install_path, file).await?;
profile
.add_project_bytes_directly(&file_override.install_path, file)
.await?;
}
Ok(())
@ -435,27 +544,47 @@ pub async fn inbound_sync(
// Updates metadata for a given ProfilePathId to the Labrinth API
// Must be an owner of the shared profile
#[tracing::instrument]
pub async fn outbound_sync(
profile_id: ProfilePathId,
) -> crate::Result<()> {
pub async fn outbound_sync(profile_id: ProfilePathId) -> crate::Result<()> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
// Get linked
let shared_profile = match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
_ => {
return Err(crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
.as_error())
}
};
// Get updated shared profile
let shared_profile = get_all().await?.into_iter().find(|x| x.id == shared_profile).ok_or_else(|| crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()))?;
let shared_profile = get_all()
.await?
.into_iter()
.find(|x| x.id == shared_profile)
.ok_or_else(|| {
crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
})?;
// Check owner
if !shared_profile.is_owned {
return Err(crate::ErrorKind::OtherError("Profile is not owned by the current user".to_string()).as_error());
return Err(crate::ErrorKind::OtherError(
"Profile is not owned by the current user".to_string(),
)
.as_error());
}
// Check if we are synced
@ -466,20 +595,35 @@ pub async fn outbound_sync(
}
let unsynced = update_data.unsynced_projects;
let projects : Vec<_> = profile.projects.clone().into_iter().filter(|(id, _)| unsynced.contains(id)).collect();
let unsynced_modrinth_projects : Vec<_> = projects.iter()
.filter_map(|(_, project)|if let ProjectMetadata::Modrinth { ref version, .. } = project.metadata {
let projects: Vec<_> = profile
.projects
.clone()
.into_iter()
.filter(|(id, _)| unsynced.contains(id))
.collect();
let unsynced_modrinth_projects: Vec<_> = projects
.iter()
.filter_map(|(_, project)| {
if let ProjectMetadata::Modrinth { ref version, .. } =
project.metadata
{
Some(&version.id)
} else {
None
}).collect();
}
})
.collect();
let unsynced_override_files : Vec<_> = projects.iter()
.filter_map(|(id, project)|if let ProjectMetadata::Inferred { ..} = project.metadata {
let unsynced_override_files: Vec<_> = projects
.iter()
.filter_map(|(id, project)| {
if let ProjectMetadata::Inferred { .. } = project.metadata {
Some(id)
} else {
None
}).collect();
}
})
.collect();
// Generate new version set
let mut new_version_set = shared_profile.versions;
@ -511,33 +655,47 @@ pub async fn outbound_sync(
let mut data = vec![]; // 'data' field, giving installation context to labrinth
for override_file in unsynced_override_files {
let path = override_file.get_inner_path_unix();
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else { continue };
let Some(name) = path.0.split('/').last().map(|x| x.to_string()) else {
continue;
};
// Load override to file
let full_path = &override_file.get_full_path(&profile_id).await?;
let file_bytes = io::read(full_path).await?;
let ext = full_path.extension().and_then(|x| x.to_str()).unwrap_or_default();
let mime = project_file_type(ext).ok_or_else(|| crate::ErrorKind::OtherError(format!("Could not determine file type for {}", ext)))?;
let ext = full_path
.extension()
.and_then(|x| x.to_str())
.unwrap_or_default();
let mime = project_file_type(ext).ok_or_else(|| {
crate::ErrorKind::OtherError(format!(
"Could not determine file type for {}",
ext
))
})?;
data.push(serde_json::json!({
"file_name": name.clone(),
"install_path": path
}));
let part = reqwest::multipart::Part::bytes(file_bytes).file_name(name.clone()).mime_str(mime)?;
let part = reqwest::multipart::Part::bytes(file_bytes)
.file_name(name.clone())
.mime_str(mime)?;
parts.push((name.clone(), part));
}
// Build multipart with 'data' field first
let mut multipart = reqwest::multipart::Form::new().percent_encode_noop();
let json_part = reqwest::multipart::Part::text(serde_json::to_string(&data)?);//mime_str("application/json")?;
let json_part =
reqwest::multipart::Part::text(serde_json::to_string(&data)?); //mime_str("application/json")?;
multipart = multipart.part("data", json_part);
for (name, part) in parts {
multipart = multipart.part(name, part);
}
let response = REQWEST_CLIENT.post(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{id}/override"),
)
let response = REQWEST_CLIENT
.post(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/{id}/override"
))
.header("Authorization", &creds.session)
.multipart(multipart);
@ -555,24 +713,37 @@ pub async fn remove_shared_profile_users(
) -> crate::Result<()> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let shared_profile = match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
_ => {
return Err(crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
.as_error())
}
};
REQWEST_CLIENT
.patch(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"),
)
.patch(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"
))
.header("Authorization", &creds.session)
.json(&serde_json::json!({
"remove_users": users,
}))
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
Ok(())
}
@ -583,24 +754,37 @@ pub async fn remove_shared_profile_links(
) -> crate::Result<()> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let shared_profile = match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
_ => {
return Err(crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
.as_error())
}
};
REQWEST_CLIENT
.patch(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"),
)
.patch(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}"
))
.header("Authorization", &creds.session)
.json(&serde_json::json!({
"remove_links": links,
}))
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
Ok(())
}
@ -610,47 +794,61 @@ pub async fn generate_share_link(
) -> crate::Result<String> {
let state = crate::State::get().await?;
let profile : Profile = profile::get(&profile_id, None).await?.ok_or_else(|| crate::ErrorKind::UnmanagedProfileError(profile_id.to_string()))?;
let profile: Profile =
profile::get(&profile_id, None).await?.ok_or_else(|| {
crate::ErrorKind::UnmanagedProfileError(profile_id.to_string())
})?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let shared_profile = match profile.metadata.linked_data {
Some(LinkedData::SharedProfile { profile_id, .. }) => profile_id,
_ => return Err(crate::ErrorKind::OtherError("Profile is not linked to a shared profile".to_string()).as_error()),
_ => {
return Err(crate::ErrorKind::OtherError(
"Profile is not linked to a shared profile".to_string(),
)
.as_error())
}
};
let response = REQWEST_CLIENT
.post(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}/share"),
)
.post(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/{shared_profile}/share"
))
.header("Authorization", &creds.session)
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
let link = response.json::<SharedProfileLink>().await?;
Ok(generate_deep_link(&link))
}
fn generate_deep_link(
link: &SharedProfileLink
) -> String {
fn generate_deep_link(link: &SharedProfileLink) -> String {
format!("modrinth://shared_profile/{}", link.id)
}
pub async fn accept_share_link(
link: String,
) -> crate::Result<()> {
pub async fn accept_share_link(link: String) -> crate::Result<()> {
let state = crate::State::get().await?;
let creds = state.credentials.read().await;
let creds = creds.0.as_ref().ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
let creds = creds
.0
.as_ref()
.ok_or_else(|| crate::ErrorKind::NoCredentialsError)?;
REQWEST_CLIENT
.post(
format!("{MODRINTH_API_URL_INTERNAL}client/profile/share/{link}/accept"),
)
.post(format!(
"{MODRINTH_API_URL_INTERNAL}client/profile/share/{link}/accept"
))
.header("Authorization", &creds.session)
.send().await?.error_for_status()?;
.send()
.await?
.error_for_status()?;
Ok(())
}

View File

@ -1,4 +1,5 @@
//! Configuration structs
pub const MODRINTH_API_URL: &str = "https://staging-api.modrinth.com/v2/";
pub const MODRINTH_API_URL_INTERNAL: &str = "https://staging-api.modrinth.com/_internal/";
pub const MODRINTH_API_URL_INTERNAL: &str =
"https://staging-api.modrinth.com/_internal/";

View File

@ -606,21 +606,18 @@ impl Profile {
relative_path: &Path,
bytes: bytes::Bytes,
) -> crate::Result<ProjectPathId> {
let state = State::get().await?;
let file_path = self
.get_profile_full_path()
.await?
.join(relative_path);
let project_path_id = ProjectPathId::new(&relative_path);
let file_path = self.get_profile_full_path().await?.join(relative_path);
let project_path_id = ProjectPathId::new(relative_path);
write(&file_path, &bytes, &state.io_semaphore).await?;
let file_name = relative_path
.file_name()
.ok_or_else(|| {
crate::ErrorKind::InputError(
format!("Could not find file name for {:?}", relative_path),
)
crate::ErrorKind::InputError(format!(
"Could not find file name for {:?}",
relative_path
))
})?
.to_string_lossy();
@ -945,8 +942,11 @@ impl Profiles {
{
let profiles = state.profiles.read().await;
for (profile_path, profile) in profiles.0.iter() {
if let Some(LinkedData::ModrinthModpack { project_id, .. }) = &profile.metadata.linked_data {
if let Some(linked_project) = &project_id {
if let Some(LinkedData::ModrinthModpack {
project_id: Some(ref linked_project),
..
}) = &profile.metadata.linked_data
{
modrinth_updatables.push((
profile_path.clone(),
linked_project.clone(),
@ -954,7 +954,6 @@ impl Profiles {
}
}
}
}
// Fetch online from Modrinth each latest version
future::try_join_all(modrinth_updatables.into_iter().map(
@ -990,7 +989,9 @@ impl Profiles {
});
if let Some(recent_version) = recent_version {
profile.sync_update_version =
Some(ProfileUpdateData::ModrinthModpack(recent_version.id.clone()));
Some(ProfileUpdateData::ModrinthModpack(
recent_version.id.clone(),
));
} else {
profile.sync_update_version = None;
}
@ -1032,11 +1033,21 @@ impl Profiles {
{
let profiles = state.profiles.read().await;
for (profile_path, profile) in profiles.0.iter() {
if let Some(LinkedData::SharedProfile { profile_id, .. }) = &profile.metadata.linked_data {
if let Some(shared_profile) = shared_profiles.iter().find(|x| x.id == *profile_id) {
if let Some(LinkedData::SharedProfile {
profile_id, ..
}) = &profile.metadata.linked_data
{
if let Some(shared_profile) =
shared_profiles.iter().find(|x| x.id == *profile_id)
{
// Check for update
let update = shared_profile::check_updated(profile_path, shared_profile).await?;
update_profiles.insert(profile_path.clone(), update);
let update = shared_profile::check_updated(
profile_path,
shared_profile,
)
.await?;
update_profiles
.insert(profile_path.clone(), update);
}
}
}
@ -1044,8 +1055,11 @@ impl Profiles {
{
let mut new_profiles = state.profiles.write().await;
for (profile_path, update) in update_profiles.iter() {
if let Some(profile) = new_profiles.0.get_mut(&profile_path) {
profile.sync_update_version = Some(ProfileUpdateData::SharedProfile(update.clone()));
if let Some(profile) = new_profiles.0.get_mut(profile_path)
{
profile.sync_update_version = Some(
ProfileUpdateData::SharedProfile(update.clone()),
);
}
}
}
@ -1054,10 +1068,12 @@ impl Profiles {
profiles.sync().await?;
for (profile_path, _) in update_profiles.iter() {
let Some(profile) = profiles.0.get(&profile_path) else { continue; };
let Some(profile) = profiles.0.get(profile_path) else {
continue;
};
emit_profile(
profile.uuid,
&profile_path,
profile_path,
&profile.metadata.name,
ProfilePayloadType::Edited,
)
@ -1065,7 +1081,8 @@ impl Profiles {
}
}
Ok::<(), crate::Error>(())
}.await;
}
.await;
match res {
Ok(()) => {}
Err(err) => {

View File

@ -1,10 +1,10 @@
// IO error
// A wrapper around the tokio IO functions that adds the path to the error message, instead of the uninformative std::io::Error.
use std::{path::Path, io::Write};
use std::{io::Write, path::Path};
use tempfile::NamedTempFile;
use tauri::async_runtime::spawn_blocking;
use tempfile::NamedTempFile;
#[derive(Debug, thiserror::Error)]
pub enum IOError {
@ -137,7 +137,8 @@ fn sync_write(
data: impl AsRef<[u8]>,
path: impl AsRef<Path>,
) -> Result<(), std::io::Error> {
let mut tempfile = NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| {
let mut tempfile =
NamedTempFile::new_in(path.as_ref().parent().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::Other,
"could not get parent directory for temporary file",

View File

@ -12,10 +12,10 @@ pub mod pack;
pub mod process;
pub mod profile;
pub mod profile_create;
pub mod profile_share;
pub mod settings;
pub mod tags;
pub mod utils;
pub mod profile_share;
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;

View File

@ -19,10 +19,8 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
// invoke('plugin:profile_share|profile_share_get_all',profile)
#[tauri::command]
pub async fn profile_share_get_all(
) -> Result<Vec<SharedProfile>> {
let res = shared_profile::get_all()
.await?;
pub async fn profile_share_get_all() -> Result<Vec<SharedProfile>> {
let res = shared_profile::get_all().await?;
Ok(res)
}
@ -30,49 +28,38 @@ pub async fn profile_share_get_all(
pub async fn profile_share_install(
profile: SharedProfile,
) -> Result<ProfilePathId> {
let res = shared_profile::install(profile)
.await?;
let res = shared_profile::install(profile).await?;
Ok(res)
}
#[tauri::command]
pub async fn profile_share_create(
path: ProfilePathId
) -> Result<()> {
shared_profile::create(path)
.await?;
pub async fn profile_share_create(path: ProfilePathId) -> Result<()> {
shared_profile::create(path).await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_inbound_sync(
path: ProfilePathId
) -> Result<()> {
shared_profile::inbound_sync(path)
.await?;
pub async fn profile_share_inbound_sync(path: ProfilePathId) -> Result<()> {
shared_profile::inbound_sync(path).await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_outbound_sync(
path : ProfilePathId
) -> Result<()> {
pub async fn profile_share_outbound_sync(path: ProfilePathId) -> Result<()> {
shared_profile::outbound_sync(path).await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_generate_share_link(
path : ProfilePathId
path: ProfilePathId,
) -> Result<String> {
let res = shared_profile::generate_share_link(path).await?;
Ok(res)
}
#[tauri::command]
pub async fn profile_share_accept_share_link(
link : String
) -> Result<()> {
pub async fn profile_share_accept_share_link(link: String) -> Result<()> {
shared_profile::accept_share_link(link).await?;
Ok(())
}
@ -80,7 +67,7 @@ pub async fn profile_share_accept_share_link(
#[tauri::command]
pub async fn profile_share_remove_users(
path: ProfilePathId,
users: Vec<String>
users: Vec<String>,
) -> Result<()> {
shared_profile::remove_shared_profile_users(path, users).await?;
Ok(())
@ -89,7 +76,7 @@ pub async fn profile_share_remove_users(
#[tauri::command]
pub async fn profile_share_remove_links(
path: ProfilePathId,
links : Vec<String>
links: Vec<String>,
) -> Result<()> {
shared_profile::remove_shared_profile_links(path, links).await?;
Ok(())

View File

@ -82,7 +82,9 @@ const install = async (e) => {
packs.length === 0 ||
!packs
.map((value) => value.metadata)
.find((pack) => pack.linked_data?.modrinth_modpack?.project_id === props.instance.project_id)
.find(
(pack) => pack.linked_data?.modrinth_modpack?.project_id === props.instance.project_id
)
) {
modLoading.value = true
await pack_install(

View File

@ -247,9 +247,9 @@ const check_valid = computed(() => {
</Button>
<div
v-tooltip="
(profile.metadata.linked_data?.modrinth_modpack.locked
|| profile.metadata.linked_data?.shared_profile
) && !profile.installedMod
(profile.metadata.linked_data?.modrinth_modpack.locked ||
profile.metadata.linked_data?.shared_profile) &&
!profile.installedMod
? 'Unpair or unlock an instance to add mods.'
: ''
"
@ -267,7 +267,8 @@ const check_valid = computed(() => {
? 'Installing...'
: profile.installedMod
? 'Installed'
: profile.metadata.linked_data?.modrinth_modpack.locked || profile.metadata.linked_data?.shared_profile
: profile.metadata.linked_data?.modrinth_modpack.locked ||
profile.metadata.linked_data?.shared_profile
? 'Paired'
: 'Install'
}}

View File

@ -28,7 +28,9 @@ const filteredVersions = computed(() => {
})
const modpackVersionModal = ref(null)
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
const installedVersion = computed(
() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id
)
const installing = computed(() => props.instance.install_stage !== 'installed')
const inProgress = ref(false)

View File

@ -24,7 +24,9 @@ defineExpose({
'version'
)
project.value = await useFetch(
`https://staging-api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
`https://staging-api.modrinth.com/v2/project/${encodeURIComponent(
version.value.project_id
)}`,
'project'
)
} else {
@ -33,7 +35,9 @@ defineExpose({
'project'
)
version.value = await useFetch(
`https://staging-api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
`https://staging-api.modrinth.com/v2/version/${encodeURIComponent(
project.value.versions[0]
)}`,
'version'
)
}

View File

@ -32,7 +32,9 @@ const getInstances = async () => {
let filters = []
for (const instance of recentInstances.value) {
if (instance.metadata.linked_data?.modrinth_modpack?.project_id) {
filters.push(`NOT"project_id"="${instance.metadata.linked_data?.modrinth_modpack?.project_id}"`)
filters.push(
`NOT"project_id"="${instance.metadata.linked_data?.modrinth_modpack?.project_id}"`
)
}
}
filter.value = filters.join(' AND ')

View File

@ -413,7 +413,10 @@
<XIcon /> Unpair
</Button>
</div>
<div v-if="props.instance.metadata.linked_data?.modrinth_modpack?.project_id" class="adjacent-input">
<div
v-if="props.instance.metadata.linked_data?.modrinth_modpack?.project_id"
class="adjacent-input"
>
<label for="change-modpack-version">
<span class="label__title">Change modpack version</span>
<span class="label__description">
@ -454,12 +457,11 @@
<label for="share-links">
<span class="label__title">Generate share link</span>
<span class="label__description">
Creates a share link to share this modpack with others. This allows them to install your instance, as well as stay up to date with any changes you make.
Creates a share link to share this modpack with others. This allows them to install your
instance, as well as stay up to date with any changes you make.
</span>
</label>
<Button id="share-links" @click="generateShareLink">
<GlobeIcon /> Share
</Button>
<Button id="share-links" @click="generateShareLink"> <GlobeIcon /> Share </Button>
</div>
<div v-if="shareLink" class="adjacent-input">
Generated link: <code>{{ shareLink }}</code>
@ -467,16 +469,10 @@
<div v-if="installedSharedProfileData.is_owned" class="table">
<div class="table-row table-head">
<div class="table-cell table-text name-cell actions-cell">
<Button class="transparent">
Name
</Button>
<Button class="transparent"> Name </Button>
</div>
</div>
<div
v-for="user in installedSharedProfileData.users"
:key="user"
class="table-row"
>
<div v-for="user in installedSharedProfileData.users" :key="user" class="table-row">
<div class="table-cell table-text name-cell">
<div class="user-content">
<span v-tooltip="`${user}`" class="title">{{ user }}</span>
@ -484,7 +480,11 @@
</div>
<div class="table-cell table-text manage">
<div v-tooltip="'Remove user'">
<Button icon-only @click="removeSharedPackUser(user)" :disabled="user === installedSharedProfileData.owner_id">
<Button
icon-only
@click="removeSharedPackUser(user)"
:disabled="user === installedSharedProfileData.owner_id"
>
<TrashIcon />
</Button>
</div>
@ -501,13 +501,22 @@
You are up to date with the shared profile.
</span>
<span class="label__description" v-else>
You have changes that have not been synced to the shared profile. Click the button to upload your changes.
You have changes that have not been synced to the shared profile. Click the button to
upload your changes.
</span>
</label>
<Button id="share-sync-sync" @click="outboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
<Button
id="share-sync-sync"
@click="outboundSyncSharedProfile"
:disabled="props.instance.sync_update_version?.is_synced"
>
<GlobeIcon /> Sync
</Button>
<Button id="share-sync-revert" @click="inboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
<Button
id="share-sync-revert"
@click="inboundSyncSharedProfile"
:disabled="props.instance.sync_update_version?.is_synced"
>
<GlobeIcon /> Revert
</Button>
</div>
@ -523,9 +532,7 @@
You are not up to date with the shared profile. Click the button to update your instance.
</span>
</label>
<Button id="share-sync-sync" @click="inboundSyncSharedProfile">
<GlobeIcon /> Sync
</Button>
<Button id="share-sync-sync" @click="inboundSyncSharedProfile"> <GlobeIcon /> Sync </Button>
</div>
</Card>
<Card>
@ -750,16 +757,20 @@ const unlinkModpack = ref(false)
const inProgress = ref(false)
const installing = computed(() => props.instance.install_stage !== 'installed')
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
const installedVersion = computed(
() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id
)
const installedVersionData = computed(() => {
if (!installedVersion.value) return null
return props.versions.find((version) => version.id === installedVersion.value)
})
const sharedProfiles = await get_all();
const sharedProfiles = await get_all()
const installedSharedProfileData = computed(() => {
if (!props.instance.metadata.linked_data?.shared_profile) return null
return sharedProfiles.find((profile) => profile.id === props.instance.metadata.linked_data?.shared_profile?.profile_id)
return sharedProfiles.find(
(profile) => profile.id === props.instance.metadata.linked_data?.shared_profile?.profile_id
)
})
watch(
@ -1046,7 +1057,6 @@ async function generateShareLink() {
async function removeSharedPackUser(user) {
await remove_users(props.instance.path, [user]).catch(handleError)
}
</script>
<style scoped lang="scss">
@ -1146,13 +1156,10 @@ async function removeSharedPackUser(user) {
align-items: center;
gap: 1rem;
.title {
color: var(--color-contrast);
font-weight: bolder;
margin-left: 1rem;
}
}
</style>

View File

@ -317,7 +317,10 @@ async function fetchProjectData() {
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}`, 'project'),
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'),
useFetch(
`https://staging-api.modrinth.com/v2/project/${route.params.id}/dependencies`,
'project'
),
get_categories().catch(handleError),
route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(),
])