Performance improvements (#114)

* Performance improvements

* run fmt

* optimize creation modal

* remove print, fix mod loader editing

* Fix library update

* update extract loading bar

* Update theseus_gui/src-tauri/src/api/metadata.rs

Co-authored-by: triphora <emma@modrinth.com>

* fix cli

---------

Co-authored-by: triphora <emma@modrinth.com>
This commit is contained in:
Geometrically
2023-05-11 18:11:15 -07:00
committed by GitHub
parent 7a0798d9d0
commit ee0c91aa80
28 changed files with 326 additions and 234 deletions

View File

@@ -25,3 +25,11 @@ pub async fn get_forge_versions() -> crate::Result<Manifest> {
Ok(tags) Ok(tags)
} }
#[tracing::instrument]
pub async fn get_quilt_versions() -> crate::Result<Manifest> {
let state = State::get().await?;
let tags = state.metadata.read().await.quilt.clone();
Ok(tags)
}

View File

@@ -13,7 +13,8 @@ pub mod tags;
pub mod data { pub mod data {
pub use crate::state::{ pub use crate::state::{
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader, DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
ProfileMetadata, Settings, Theme, WindowSize, ModrinthProject, ModrinthTeamMember, ModrinthUser, ModrinthVersion,
ProfileMetadata, ProjectMetadata, Settings, Theme, WindowSize,
}; };
} }

View File

@@ -6,7 +6,8 @@ use crate::event::emit::{
}; };
use crate::event::{LoadingBarId, LoadingBarType}; use crate::event::{LoadingBarId, LoadingBarType};
use crate::state::{ use crate::state::{
LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType, LinkedData, ModrinthProject, ModrinthVersion, Profile, ProfileInstallStage,
SideType,
}; };
use crate::util::fetch::{ use crate::util::fetch::{
fetch, fetch_advanced, fetch_json, fetch_mirrors, write, write_cached_icon, fetch, fetch_advanced, fetch_json, fetch_mirrors, write, write_cached_icon,
@@ -325,6 +326,7 @@ async fn install_pack(
prof.metadata.icon = icon.clone(); prof.metadata.icon = icon.clone();
prof.metadata.game_version = game_version.clone(); prof.metadata.game_version = game_version.clone();
prof.metadata.loader_version = loader_version.clone(); prof.metadata.loader_version = loader_version.clone();
prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla);
async { Ok(()) } async { Ok(()) }
}) })
@@ -409,83 +411,79 @@ async fn install_pack(
) )
.await?; .await?;
let extract_overrides = |overrides: String| async {
let reader = Cursor::new(&file);
let mut overrides_zip =
ZipFileReader::new(reader).await.map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError(
"Failed extract overrides Zip".to_string(),
))
})?;
let profile = profile.clone();
async move {
for index in 0..overrides_zip.file().entries().len() {
let file = overrides_zip
.file()
.entries()
.get(index)
.unwrap()
.entry()
.clone();
let file_path = PathBuf::from(file.filename());
if file.filename().starts_with(&overrides)
&& !file.filename().ends_with('/')
{
// Reads the file into the 'content' variable
let mut content = Vec::new();
let mut reader =
overrides_zip.entry(index).await?;
reader
.read_to_end_checked(&mut content, &file)
.await?;
let mut new_path = PathBuf::new();
let components = file_path.components().skip(1);
for component in components {
new_path.push(component);
}
if new_path.file_name().is_some() {
write(
&profile.join(new_path),
&content,
&state.io_semaphore,
)
.await?;
}
}
}
Ok::<(), crate::Error>(())
}
.await
};
emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
.await?; .await?;
extract_overrides("overrides".to_string()).await?;
extract_overrides("client_overrides".to_string()).await?;
emit_loading(
&loading_bar,
29.9,
Some("Done extracting overrides"),
)
.await?;
if let Some(profile) = let mut total_len = 0;
crate::api::profile::get(&profile).await?
for index in 0..zip_reader.file().entries().len() {
let file =
zip_reader.file().entries().get(index).unwrap().entry();
if (file.filename().starts_with("overrides")
|| file.filename().starts_with("client_overrides"))
&& !file.filename().ends_with('/')
{
total_len += 1;
}
}
for index in 0..zip_reader.file().entries().len() {
let file = zip_reader
.file()
.entries()
.get(index)
.unwrap()
.entry()
.clone();
let file_path = PathBuf::from(file.filename());
if (file.filename().starts_with("overrides")
|| file.filename().starts_with("client_overrides"))
&& !file.filename().ends_with('/')
{
// Reads the file into the 'content' variable
let mut content = Vec::new();
let mut reader = zip_reader.entry(index).await?;
reader.read_to_end_checked(&mut content, &file).await?;
let mut new_path = PathBuf::new();
let components = file_path.components().skip(1);
for component in components {
new_path.push(component);
}
if new_path.file_name().is_some() {
write(
&profile.join(new_path),
&content,
&state.io_semaphore,
)
.await?;
}
emit_loading(
&loading_bar,
30.0 / total_len as f64,
Some(&format!(
"Extracting override {}/{}",
index, total_len
)),
)
.await?;
}
}
if let Some(profile_val) =
crate::api::profile::get(&profile, None).await?
{ {
tokio::try_join!( Profile::sync_projects_task(profile.clone());
super::profile::sync(&profile.path), crate::launcher::install_minecraft(
crate::launcher::install_minecraft( &profile_val,
&profile, Some(loading_bar),
Some(loading_bar) )
), .await?;
)?;
} }
Ok::<PathBuf, crate::Error>(profile.clone()) Ok::<PathBuf, crate::Error>(profile.clone())

View File

@@ -12,6 +12,7 @@ pub use crate::{
state::{JavaSettings, Profile}, state::{JavaSettings, Profile},
State, State,
}; };
use std::collections::HashMap;
use std::{ use std::{
future::Future, future::Future,
path::{Path, PathBuf}, path::{Path, PathBuf},
@@ -40,11 +41,21 @@ pub async fn remove(path: &Path) -> crate::Result<()> {
/// Get a profile by path, /// Get a profile by path,
#[tracing::instrument] #[tracing::instrument]
pub async fn get(path: &Path) -> crate::Result<Option<Profile>> { pub async fn get(
path: &Path,
clear_projects: Option<bool>,
) -> crate::Result<Option<Profile>> {
let state = State::get().await?; let state = State::get().await?;
let profiles = state.profiles.read().await; let profiles = state.profiles.read().await;
let mut profile = profiles.0.get(path).cloned();
Ok(profiles.0.get(path).cloned()) if clear_projects.unwrap_or(false) {
if let Some(profile) = &mut profile {
profile.projects = HashMap::new();
}
}
Ok(profile)
} }
/// Edit a profile using a given asynchronous closure /// Edit a profile using a given asynchronous closure
@@ -79,11 +90,23 @@ where
/// Get a copy of the profile set /// Get a copy of the profile set
#[tracing::instrument] #[tracing::instrument]
pub async fn list() -> crate::Result<std::collections::HashMap<PathBuf, Profile>> pub async fn list(
{ clear_projects: Option<bool>,
) -> crate::Result<HashMap<PathBuf, Profile>> {
let state = State::get().await?; let state = State::get().await?;
let profiles = state.profiles.read().await; let profiles = state.profiles.read().await;
Ok(profiles.0.clone()) Ok(profiles
.0
.clone()
.into_iter()
.map(|mut x| {
if clear_projects.unwrap_or(false) {
x.1.projects = HashMap::new();
}
x
})
.collect())
} }
/// Query + sync profile's projects with the UI from the FS /// Query + sync profile's projects with the UI from the FS
@@ -117,7 +140,7 @@ pub async fn sync(path: &Path) -> crate::Result<()> {
/// Installs/Repairs a profile /// Installs/Repairs a profile
#[tracing::instrument] #[tracing::instrument]
pub async fn install(path: &Path) -> crate::Result<()> { pub async fn install(path: &Path) -> crate::Result<()> {
let profile = get(path).await?; let profile = get(path, None).await?;
if let Some(profile) = profile { if let Some(profile) = profile {
crate::launcher::install_minecraft(&profile, None).await?; crate::launcher::install_minecraft(&profile, None).await?;
@@ -398,7 +421,7 @@ pub async fn run_credentials(
let state = State::get().await?; let state = State::get().await?;
let settings = state.settings.read().await; let settings = state.settings.read().await;
let metadata = state.metadata.read().await; let metadata = state.metadata.read().await;
let profile = get(path).await?.ok_or_else(|| { let profile = get(path, None).await?.ok_or_else(|| {
crate::ErrorKind::OtherError(format!( crate::ErrorKind::OtherError(format!(
"Tried to run a nonexistent or unloaded profile at path {}!", "Tried to run a nonexistent or unloaded profile at path {}!",
path.display() path.display()

View File

@@ -111,6 +111,8 @@ impl Drop for LoadingBar {
let loader_uuid = self.loading_bar_uuid; let loader_uuid = self.loading_bar_uuid;
let event = self.bar_type.clone(); let event = self.bar_type.clone();
let fraction = self.current / self.total; let fraction = self.current / self.total;
#[cfg(feature = "cli")]
let cli_progress_bar = self.cli_progress_bar.clone(); let cli_progress_bar = self.cli_progress_bar.clone();
tokio::spawn(async move { tokio::spawn(async move {

View File

@@ -60,6 +60,18 @@ pub async fn install_minecraft(
existing_loading_bar: Option<LoadingBarId>, existing_loading_bar: Option<LoadingBarId>,
) -> crate::Result<()> { ) -> crate::Result<()> {
Box::pin(async move { Box::pin(async move {
let loading_bar = init_or_edit_loading(
existing_loading_bar,
LoadingBarType::MinecraftDownload {
// If we are downloading minecraft for a profile, provide its name and uuid
profile_name: profile.metadata.name.clone(),
profile_path: profile.path.clone(),
},
100.0,
"Downloading Minecraft",
)
.await?;
crate::api::profile::edit(&profile.path, |prof| { crate::api::profile::edit(&profile.path, |prof| {
prof.install_stage = ProfileInstallStage::Installing; prof.install_stage = ProfileInstallStage::Installing;
@@ -90,18 +102,6 @@ pub async fn install_minecraft(
format!("{}-{}", version.id.clone(), it.id.clone()) format!("{}-{}", version.id.clone(), it.id.clone())
}); });
let loading_bar = init_or_edit_loading(
existing_loading_bar,
LoadingBarType::MinecraftDownload {
// If we are downloading minecraft for a profile, provide its name and uuid
profile_name: profile.metadata.name.clone(),
profile_path: profile.path.clone(),
},
100.0,
"Downloading Minecraft",
)
.await?;
// Download version info (5) // Download version info (5)
let mut version_info = download::download_version_info( let mut version_info = download::download_version_info(
&state, &state,

View File

@@ -253,9 +253,11 @@ impl Children {
let child = child.clone(); let child = child.clone();
let child = child.write().await; let child = child.write().await;
if child.current_child.write().await.try_wait()?.is_none() { if child.current_child.write().await.try_wait()?.is_none() {
if let Some(prof) = if let Some(prof) = crate::api::profile::get(
crate::api::profile::get(&child.profile_path.clone()) &child.profile_path.clone(),
.await? None,
)
.await?
{ {
profiles.push(prof); profiles.push(prof);
} }

View File

@@ -210,7 +210,7 @@ impl Profile {
tokio::task::spawn(async move { tokio::task::spawn(async move {
let res = async { let res = async {
let state = State::get().await?; let state = State::get().await?;
let profile = crate::api::profile::get(&path).await?; let profile = crate::api::profile::get(&path, None).await?;
if let Some(profile) = profile { if let Some(profile) = profile {
let paths = profile.get_profile_project_paths()?; let paths = profile.get_profile_project_paths()?;

View File

@@ -257,7 +257,7 @@ impl ProfileList {
_args: &crate::Args, _args: &crate::Args,
_largs: &ProfileCommand, _largs: &ProfileCommand,
) -> Result<()> { ) -> Result<()> {
let profiles = profile::list().await?; let profiles = profile::list(None).await?;
let rows = profiles.values().map(ProfileRow::from); let rows = profiles.values().map(ProfileRow::from);
let table = table(rows).with( let table = table(rows).with(

View File

@@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] }
regex = "1.5" regex = "1.5"
[dependencies] [dependencies]
theseus = { path = "../../theseus", features = ["tauri", "cli"] } theseus = { path = "../../theseus", features = ["tauri"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@@ -48,7 +48,7 @@ pub async fn jre_get_optimal_jre_key(profile: Profile) -> Result<String> {
// The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists) // The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists)
#[tauri::command] #[tauri::command]
pub async fn jre_get_optimal_jre_key_by_path(path: &Path) -> Result<String> { pub async fn jre_get_optimal_jre_key_by_path(path: &Path) -> Result<String> {
let profile = profile::get(path).await?.ok_or_else(|| { let profile = profile::get(path, Some(true)).await?.ok_or_else(|| {
TheseusSerializableError::NoProfileFound(path.display().to_string()) TheseusSerializableError::NoProfileFound(path.display().to_string())
})?; })?;
Ok(jre::get_optimal_jre_key(&profile).await?) Ok(jre::get_optimal_jre_key(&profile).await?)

View File

@@ -19,3 +19,9 @@ pub async fn metadata_get_fabric_versions() -> Result<Manifest> {
pub async fn metadata_get_forge_versions() -> Result<Manifest> { pub async fn metadata_get_forge_versions() -> Result<Manifest> {
Ok(theseus::metadata::get_forge_versions().await?) Ok(theseus::metadata::get_forge_versions().await?)
} }
/// Gets the quilt versions from daedalus
#[tauri::command]
pub async fn metadata_get_quilt_versions() -> Result<Manifest> {
Ok(theseus::metadata::get_quilt_versions().await?)
}

View File

@@ -16,8 +16,11 @@ pub async fn profile_remove(path: &Path) -> Result<()> {
// Get a profile by path // Get a profile by path
// invoke('profile_add_path',path) // invoke('profile_add_path',path)
#[tauri::command] #[tauri::command]
pub async fn profile_get(path: &Path) -> Result<Option<Profile>> { pub async fn profile_get(
let res = profile::get(path).await?; path: &Path,
clear_projects: Option<bool>,
) -> Result<Option<Profile>> {
let res = profile::get(path, clear_projects).await?;
Ok(res) Ok(res)
} }
@@ -25,11 +28,32 @@ pub async fn profile_get(path: &Path) -> Result<Option<Profile>> {
// invoke('profile_list') // invoke('profile_list')
#[tauri::command] #[tauri::command]
pub async fn profile_list( pub async fn profile_list(
clear_projects: Option<bool>,
) -> Result<std::collections::HashMap<PathBuf, Profile>> { ) -> Result<std::collections::HashMap<PathBuf, Profile>> {
let res = profile::list().await?; let res = profile::list(clear_projects).await?;
Ok(res) Ok(res)
} }
#[tauri::command]
pub async fn profile_check_installed(
path: &Path,
project_id: String,
) -> Result<bool> {
let profile = profile_get(path, None).await?;
if let Some(profile) = profile {
Ok(profile.projects.into_iter().any(|(_, project)| {
if let ProjectMetadata::Modrinth { project, .. } = &project.metadata
{
project.id == project_id
} else {
false
}
}))
} else {
Ok(false)
}
}
/// Syncs a profile's in memory state with the state on the disk /// Syncs a profile's in memory state with the state on the disk
/// // invoke('profile_sync') /// // invoke('profile_sync')
#[tauri::command] #[tauri::command]

View File

@@ -93,6 +93,7 @@ fn main() {
api::profile::profile_run_credentials, api::profile::profile_run_credentials,
api::profile::profile_run_wait_credentials, api::profile::profile_run_wait_credentials,
api::profile::profile_edit, api::profile::profile_edit,
api::profile::profile_check_installed,
api::pack::pack_install_version_id, api::pack::pack_install_version_id,
api::pack::pack_install_file, api::pack::pack_install_file,
api::auth::auth_authenticate_begin_flow, api::auth::auth_authenticate_begin_flow,
@@ -133,6 +134,7 @@ fn main() {
api::metadata::metadata_get_game_versions, api::metadata::metadata_get_game_versions,
api::metadata::metadata_get_fabric_versions, api::metadata::metadata_get_fabric_versions,
api::metadata::metadata_get_forge_versions, api::metadata::metadata_get_forge_versions,
api::metadata::metadata_get_quilt_versions,
api::logs::logs_get_logs, api::logs::logs_get_logs,
api::logs::logs_get_logs_by_datetime, api::logs::logs_get_logs_by_datetime,
api::logs::logs_get_stdout_by_datetime, api::logs::logs_get_stdout_by_datetime,

View File

@@ -103,7 +103,7 @@ const modsRow = ref(null)
margin-right: auto; margin-right: auto;
margin-top: 0.8rem; margin-top: 0.8rem;
scroll-behavior: smooth; scroll-behavior: smooth;
overflow-y: scroll; overflow-y: auto;
} }
} }

View File

@@ -63,7 +63,7 @@ export default defineComponent({
background: props.color || undefined, background: props.color || undefined,
backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`, backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`,
transition: 'width 0.1s, height 0.4s, opacity 0.4s', transition: 'width 0.1s, height 0.4s, opacity 0.4s',
zIndex: 99, zIndex: 6,
}, },
}, },
slots slots

View File

@@ -64,7 +64,7 @@ const install = async (e) => {
) )
if (props.instance.project_type === 'modpack') { if (props.instance.project_type === 'modpack') {
const packs = Object.values(await list()) const packs = Object.values(await list(true))
if ( if (
packs.length === 0 || packs.length === 0 ||
@@ -125,9 +125,14 @@ await process_listener((e) => {
<template> <template>
<div class="instance"> <div class="instance">
<Card v-if="props.small" class="instance-small-card button-base"> <Card v-if="props.small" class="instance-small-card button-base" @click="seeInstance">
<Avatar <Avatar
:src="convertFileSrc(props.instance.metadata.icon)" :src="
!props.instance.metadata.icon ||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
? props.instance.metadata.icon
: convertFileSrc(instance.metadata?.icon)
"
:alt="props.instance.metadata.name" :alt="props.instance.metadata.name"
size="sm" size="sm"
/> />
@@ -150,9 +155,10 @@ await process_listener((e) => {
size="none" size="none"
:src=" :src="
props.instance.metadata props.instance.metadata
? props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http') ? !props.instance.metadata.icon ||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
? props.instance.metadata.icon ? props.instance.metadata.icon
: convertFileSrc(props.instance.metadata?.icon) : convertFileSrc(instance.metadata?.icon)
: props.instance.icon_url : props.instance.icon_url
" "
alt="Mod card" alt="Mod card"
@@ -166,25 +172,27 @@ await process_listener((e) => {
</p> </p>
</div> </div>
</Card> </Card>
<div <template v-if="!props.small">
v-if="props.instance.metadata && playing === false && modLoading === false" <div
class="install cta button-base" v-if="props.instance.metadata && playing === false && modLoading === false"
@click="play" class="install cta button-base"
> @click="play"
<PlayIcon /> >
</div> <PlayIcon />
<div v-else-if="modLoading === true && playing === false" class="cta loading"> </div>
<AnimatedLogo class="loading" /> <div v-else-if="modLoading === true && playing === false" class="cta loading">
</div> <AnimatedLogo class="loading" />
<div </div>
v-else-if="playing === true" <div
class="stop cta button-base" v-else-if="playing === true"
@click="stop" class="stop cta button-base"
@mousehover="checkProcess" @click="stop"
> @mousehover="checkProcess"
<XIcon /> >
</div> <XIcon />
<div v-else class="install cta buttonbase" @click="install"><SaveIcon /></div> </div>
<div v-else class="install cta buttonbase" @click="install"><SaveIcon /></div>
</template>
<InstallConfirmModal ref="confirmModal" /> <InstallConfirmModal ref="confirmModal" />
</div> </div>
</template> </template>
@@ -209,10 +217,6 @@ await process_listener((e) => {
font-weight: bolder; font-weight: bolder;
} }
} }
.cta {
display: none;
}
} }
.instance { .instance {

View File

@@ -86,13 +86,13 @@ import {
CodeIcon, CodeIcon,
Checkbox, Checkbox,
} from 'omorphia' } from 'omorphia'
import { computed, ref } from 'vue' import { computed, ref, shallowRef } from 'vue'
import { get_game_versions, get_loaders } from '@/helpers/tags' import { get_game_versions, get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile' import { create } from '@/helpers/profile'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { tauri } from '@tauri-apps/api' import { tauri } from '@tauri-apps/api'
import { get_fabric_versions, get_forge_versions } from '@/helpers/metadata' import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata'
const router = useRouter() const router = useRouter()
@@ -129,20 +129,27 @@ defineExpose({
}, },
}) })
const all_game_versions = ref(await get_game_versions()) const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] =
await Promise.all([
get_fabric_versions().then(shallowRef),
get_forge_versions().then(shallowRef),
get_quilt_versions().then(shallowRef),
get_game_versions().then(shallowRef),
get_loaders()
.then((value) =>
value
.filter((item) => item.supported_project_types.includes('modpack'))
.map((item) => item.name.toLowerCase())
)
.then(ref),
])
const game_versions = computed(() => { const game_versions = computed(() => {
return all_game_versions.value return all_game_versions.value
.filter((item) => item.version_type === 'release' || showSnapshots.value) .filter((item) => item.version_type === 'release' || showSnapshots.value)
.map((item) => item.version) .map((item) => item.version)
}) })
const loaders = ref(
await get_loaders().then((value) =>
value
.filter((item) => item.supported_project_types.includes('modpack'))
.map((item) => item.name.toLowerCase())
)
)
const modal = ref(null) const modal = ref(null)
const check_valid = computed(() => { const check_valid = computed(() => {
@@ -191,9 +198,6 @@ const reset_icon = () => {
display_icon.value = null display_icon.value = null
} }
const fabric_versions = ref(await get_fabric_versions())
const forge_versions = ref(await get_forge_versions())
const selectable_versions = computed(() => { const selectable_versions = computed(() => {
if (game_version.value) { if (game_version.value) {
if (loader.value === 'fabric') { if (loader.value === 'fabric') {
@@ -202,6 +206,8 @@ const selectable_versions = computed(() => {
return forge_versions.value.gameVersions return forge_versions.value.gameVersions
.find((item) => item.id === game_version.value) .find((item) => item.id === game_version.value)
.loaders.map((item) => item.id) .loaders.map((item) => item.id)
} else if (loader.value === 'quilt') {
return quilt_versions.value.gameVersions[0].loaders.map((item) => item.id)
} }
} }
return [] return []

View File

@@ -11,14 +11,15 @@ import {
RightArrowIcon, RightArrowIcon,
CheckIcon, CheckIcon,
} from 'omorphia' } from 'omorphia'
import { computed, ref } from 'vue' import { computed, ref, shallowRef } from 'vue'
import { add_project_from_version as installMod, list } from '@/helpers/profile' import { add_project_from_version as installMod, check_installed, list } from '@/helpers/profile'
import { tauri } from '@tauri-apps/api' import { tauri } from '@tauri-apps/api'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { create } from '@/helpers/profile' import { create } from '@/helpers/profile'
import { checkInstalled, installVersionDependencies } from '@/helpers/utils' import { installVersionDependencies } from '@/helpers/utils'
const router = useRouter() const router = useRouter()
const versions = ref([]) const versions = ref([])
const project = ref('') const project = ref('')
@@ -33,15 +34,17 @@ const gameVersion = ref(null)
const creatingInstance = ref(false) const creatingInstance = ref(false)
defineExpose({ defineExpose({
show: (projectId, selectedVersion) => { show: async (projectId, selectedVersion) => {
project.value = projectId project.value = projectId
versions.value = selectedVersion versions.value = selectedVersion
installModal.value.show() installModal.value.show()
searchFilter.value = '' searchFilter.value = ''
profiles.value = await getData()
}, },
}) })
const profiles = ref(await list().then(Object.values)) const profiles = shallowRef(await getData())
async function install(instance) { async function install(instance) {
instance.installing = true instance.installing = true
@@ -59,8 +62,10 @@ async function install(instance) {
instance.installing = false instance.installing = false
} }
const filteredVersions = computed(() => { async function getData() {
const filtered = profiles.value const projects = await list(true).then(Object.values)
const filtered = projects
.filter((profile) => { .filter((profile) => {
return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase()) return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase())
}) })
@@ -69,17 +74,20 @@ const filteredVersions = computed(() => {
versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) && versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) &&
versions.value versions.value
.flatMap((v) => v.loaders) .flatMap((v) => v.loaders)
.some((value) => value === profile.metadata.loader || value === 'minecraft') .some(
(value) =>
value === profile.metadata.loader || ['minecraft', 'iris', 'optifine'].includes(value)
)
) )
}) })
filtered.map((profile) => { for (let profile of filtered) {
profile.installing = false profile.installing = false
profile.installedMod = checkInstalled(profile, project.value) profile.installedMod = await check_installed(profile.path, project.value)
}) }
return filtered return filtered
}) }
const toggleCreation = () => { const toggleCreation = () => {
showCreation.value = !showCreation.value showCreation.value = !showCreation.value
@@ -140,7 +148,7 @@ const check_valid = computed(() => {
<div class="modal-body"> <div class="modal-body">
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" /> <input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
<div class="profiles"> <div class="profiles">
<div v-for="profile in filteredVersions" :key="profile.metadata.name" class="option"> <div v-for="profile in profiles" :key="profile.metadata.name" class="option">
<Button <Button
color="raised" color="raised"
class="profile-button" class="profile-button"

View File

@@ -17,3 +17,9 @@ export async function get_fabric_versions() {
export async function get_forge_versions() { export async function get_forge_versions() {
return await invoke('metadata_get_forge_versions') return await invoke('metadata_get_forge_versions')
} }
// Gets the quilt versions from daedalus
// Returns Manifest
export async function get_quilt_versions() {
return await invoke('metadata_get_quilt_versions')
}

View File

@@ -32,14 +32,18 @@ export async function remove(path) {
// Get a profile by path // Get a profile by path
// Returns a Profile // Returns a Profile
export async function get(path) { export async function get(path, clearProjects) {
return await invoke('profile_get', { path }) return await invoke('profile_get', { path, clearProjects })
} }
// Get a copy of the profile set // Get a copy of the profile set
// Returns hashmap of path -> Profile // Returns hashmap of path -> Profile
export async function list() { export async function list(clearProjects) {
return await invoke('profile_list') return await invoke('profile_list', { clearProjects })
}
export async function check_installed(path, projectId) {
return await invoke('profile_check_installed', { path, projectId })
} }
// Syncs a profile with the disk // Syncs a profile with the disk

View File

@@ -1,4 +1,4 @@
import { add_project_from_version as installMod } from '@/helpers/profile' import { add_project_from_version as installMod, check_installed } from '@/helpers/profile'
import { ofetch } from 'ofetch' import { ofetch } from 'ofetch'
export const releaseColor = (releaseType) => { export const releaseColor = (releaseType) => {
@@ -14,17 +14,13 @@ export const releaseColor = (releaseType) => {
} }
} }
export const checkInstalled = (profile, projectId) => {
return Object.values(profile.projects).some((p) => p.metadata?.project?.id === projectId)
}
export const installVersionDependencies = async (profile, version) => { export const installVersionDependencies = async (profile, version) => {
for (const dep of version.dependencies) { for (const dep of version.dependencies) {
if (dep.version_id) { if (dep.version_id) {
if (checkInstalled(profile, dep.project_id)) continue if (await check_installed(profile.path, dep.project_id)) continue
await installMod(profile.path, dep.version_id) await installMod(profile.path, dep.version_id)
} else { } else {
if (checkInstalled(profile, dep.project_id)) continue if (await check_installed(profile.path, dep.project_id)) continue
const depVersions = await ofetch( const depVersions = await ofetch(
`https://api.modrinth.com/v2/project/${dep.project_id}/version` `https://api.modrinth.com/v2/project/${dep.project_id}/version`
) )

View File

@@ -167,15 +167,17 @@ watch(
v-if=" v-if="
showLoaders && showLoaders &&
searchStore.projectType !== 'datapack' && searchStore.projectType !== 'datapack' &&
searchStore.projectType !== 'resourcepack' && searchStore.projectType !== 'resourcepack'
searchStore.projectType !== 'shader'
" "
class="loaders" class="loaders"
> >
<h2>Loaders</h2> <h2>Loaders</h2>
<div <div
v-for="loader in loaders.filter((l) => v-for="loader in loaders.filter(
l.supported_project_types?.includes(searchStore.projectType) (l) =>
(searchStore.projectType !== 'mod' &&
l.supported_project_types?.includes(searchStore.projectType)) ||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
)" )"
:key="loader" :key="loader"
> >

View File

@@ -16,11 +16,11 @@ const breadcrumbs = useBreadcrumbs()
breadcrumbs.setRootContext({ name: 'Home', link: route.path }) breadcrumbs.setRootContext({ name: 'Home', link: route.path })
const recentInstances = shallowRef(Object.values(await list())) const recentInstances = shallowRef([])
const getInstances = async () => { const getInstances = async () => {
filter.value = '' filter.value = ''
const profiles = await list() const profiles = await list(true)
recentInstances.value = Object.values(profiles) recentInstances.value = Object.values(profiles)
const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id) const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id)
@@ -47,8 +47,8 @@ await getInstances()
await Promise.all([getFeaturedModpacks(), getFeaturedMods()]) await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
const unlisten = await profile_listener(async (e) => { const unlisten = await profile_listener(async (e) => {
await getInstances()
if (e.event === 'created' || e.event === 'removed') { if (e.event === 'created' || e.event === 'removed') {
await getInstances()
await Promise.all([getFeaturedModpacks(), getFeaturedMods()]) await Promise.all([getFeaturedModpacks(), getFeaturedMods()])
} }
}) })

View File

@@ -4,21 +4,19 @@ import GridDisplay from '@/components/GridDisplay.vue'
import { list } from '@/helpers/profile.js' import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { loading_listener } from '@/helpers/events.js' import { profile_listener } from '@/helpers/events.js'
const route = useRoute() const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
breadcrumbs.setRootContext({ name: 'Library', link: route.path }) breadcrumbs.setRootContext({ name: 'Library', link: route.path })
const profiles = await list() const profiles = await list(true)
const instances = shallowRef(Object.values(profiles)) const instances = shallowRef(Object.values(profiles))
loading_listener(async (profile) => { profile_listener(async () => {
if (profile.event === 'loaded') { const profiles = await list(true)
const profiles = await list() instances.value = Object.values(profiles)
instances.value = Object.values(profiles)
}
}) })
</script> </script>

View File

@@ -5,7 +5,8 @@
<Avatar <Avatar
size="lg" size="lg"
:src=" :src="
instance.metadata.icon && instance.metadata.icon.startsWith('http') !instance.metadata.icon ||
(instance.metadata.icon && instance.metadata.icon.startsWith('http'))
? instance.metadata.icon ? instance.metadata.icon
: convertFileSrc(instance.metadata?.icon) : convertFileSrc(instance.metadata?.icon)
" "

View File

@@ -221,13 +221,13 @@ import {
} from '@/assets/external' } from '@/assets/external'
import { get_categories, get_loaders } from '@/helpers/tags' import { get_categories, get_loaders } from '@/helpers/tags'
import { install as packInstall } from '@/helpers/pack' import { install as packInstall } from '@/helpers/pack'
import { list, add_project_from_version as installMod } from '@/helpers/profile' import { list, add_project_from_version as installMod, check_installed } from '@/helpers/profile'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { ofetch } from 'ofetch' import { ofetch } from 'ofetch'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ref, shallowRef, watch } from 'vue' import { ref, shallowRef, watch } from 'vue'
import { checkInstalled, installVersionDependencies } from '@/helpers/utils' import { installVersionDependencies } from '@/helpers/utils'
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue' import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue' import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
import Instance from '@/components/ui/Instance.vue' import Instance from '@/components/ui/Instance.vue'
@@ -242,19 +242,19 @@ const breadcrumbs = useBreadcrumbs()
const confirmModal = ref(null) const confirmModal = ref(null)
const modInstallModal = ref(null) const modInstallModal = ref(null)
const loaders = ref(await get_loaders())
const categories = ref(await get_categories())
const instance = ref(searchStore.instanceContext) const instance = ref(searchStore.instanceContext)
const installing = ref(false) const installing = ref(false)
const [data, versions, members, dependencies] = await Promise.all([ const [data, versions, members, dependencies, categories, loaders] = await Promise.all([
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}`).then(shallowRef), ofetch(`https://api.modrinth.com/v2/project/${route.params.id}`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`).then(shallowRef), ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`).then(shallowRef), ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`).then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef), ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef),
get_loaders().then(ref),
get_categories().then(ref),
]) ])
const installed = ref(instance.value && checkInstalled(instance.value, data.value.id)) const installed = ref(instance.value && (await check_installed(instance.value.path, data.value.id)))
breadcrumbs.setName('Project', data.value.title) breadcrumbs.setName('Project', data.value.title)
@@ -284,7 +284,7 @@ async function install(version) {
} }
if (data.value.project_type === 'modpack') { if (data.value.project_type === 'modpack') {
const packs = Object.values(await list()) const packs = Object.values(await list(true))
if ( if (
packs.length === 0 || packs.length === 0 ||
!packs !packs
@@ -317,7 +317,7 @@ async function install(version) {
await installMod(instance.value.path, queuedVersionData.id) await installMod(instance.value.path, queuedVersionData.id)
} }
installVersionDependencies(instance.value, queuedVersionData) await installVersionDependencies(instance.value, queuedVersionData)
installed.value = true installed.value = true
} else { } else {

View File

@@ -61,7 +61,7 @@ async fn main() -> theseus::Result<()> {
// Clear profiles // Clear profiles
println!("Clearing profiles."); println!("Clearing profiles.");
{ {
let h = profile::list().await?; let h = profile::list(None).await?;
for (path, _) in h.into_iter() { for (path, _) in h.into_iter() {
profile::remove(&path).await?; profile::remove(&path).await?;
} }
@@ -69,51 +69,52 @@ async fn main() -> theseus::Result<()> {
println!("Creating/adding profile."); println!("Creating/adding profile.");
let name = "Example".to_string(); // let name = "Example".to_string();
let game_version = "1.19.2".to_string(); // let game_version = "1.19.2".to_string();
let modloader = ModLoader::Vanilla; // let modloader = ModLoader::Vanilla;
let loader_version = "stable".to_string(); // let loader_version = "stable".to_string();
//
let profile_path = profile_create( // let profile_path = profile_create(
name.clone(), // name.clone(),
game_version, // game_version,
modloader, // modloader,
Some(loader_version), // Some(loader_version),
None, // None,
None, // None,
None, // None,
None, // None,
) // )
.await?; // .await?;
//
install(&profile_path).await.unwrap(); // install(&profile_path).await.unwrap();
// let mut value = list().await?; // let mut value = list().await?;
// let profile_path = value.iter().next().map(|x| x.0).unwrap(); // let profile_path = value.iter().next().map(|x| x.0).unwrap();
println!("Adding sodium"); // println!("Adding sodium");
let sodium_path = profile::add_project_from_version( // let sodium_path = profile::add_project_from_version(
&profile_path, // &profile_path,
"rAfhHfow".to_string(), // "rAfhHfow".to_string(),
) // )
.await?; // .await?;
//
let mod_menu_path = profile::add_project_from_version( // let mod_menu_path = profile::add_project_from_version(
&profile_path, // &profile_path,
"gSoPJyVn".to_string(), // "gSoPJyVn".to_string(),
) // )
.await?; // .await?;
//
println!("Disabling sodium"); // println!("Disabling sodium");
profile::toggle_disable_project(&profile_path, &sodium_path).await?; // profile::toggle_disable_project(&profile_path, &sodium_path).await?;
// profile::remove_project(&profile_path, &mod_menu_path).await?; // profile::remove_project(&profile_path, &mod_menu_path).await?;
// let profile_path = pack::install_pack_from_version_id( let profile_path = pack::install_pack_from_version_id(
// "zroFQG1k".to_string(), "CeeCkHke".to_string(),
// Some("Technical Electrical".to_string()), "Technical Electrical".to_string(),
// ) None,
// .await )
// .unwrap(); .await
.unwrap();
// async closure for testing any desired edits // async closure for testing any desired edits
// (ie: changing the java runtime of an added profile) // (ie: changing the java runtime of an added profile)