diff --git a/Cargo.lock b/Cargo.lock index 588936200..519375f7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -689,9 +689,9 @@ dependencies = [ [[package]] name = "daedalus" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c9c34a2d4904bcaa4cfa5f62b38c915c106fdc92a6a66276ae2bd5ba1b2527" +checksum = "ab1ff8f873475996ff3d755659e5e0fbe5a2d02d6fc84ff2b625874a8c446973" dependencies = [ "bincode", "bytes", diff --git a/theseus/Cargo.toml b/theseus/Cargo.toml index a2a02d005..aa4a757ea 100644 --- a/theseus/Cargo.toml +++ b/theseus/Cargo.toml @@ -19,7 +19,7 @@ zip = "0.5" async_zip = { version = "0.0.13", features = ["full"] } chrono = { version = "0.4.19", features = ["serde"] } -daedalus = { version = "0.1.18" } +daedalus = { version = "0.1.20" } dirs = "4.0" log = "0.4.14" diff --git a/theseus/src/api/jre.rs b/theseus/src/api/jre.rs index a29f44469..90dea9851 100644 --- a/theseus/src/api/jre.rs +++ b/theseus/src/api/jre.rs @@ -69,8 +69,8 @@ pub async fn get_optimal_jre_key(profile: &Profile) -> crate::Result { .map(|it| it.major_version) .unwrap_or(0) { - 0..=16 => JAVA_8_KEY.to_string(), - 17 => JAVA_17_KEY.to_string(), + 0..=15 => JAVA_8_KEY.to_string(), + 16..=17 => JAVA_17_KEY.to_string(), _ => JAVA_18PLUS_KEY.to_string(), }; Ok(optimal_key) diff --git a/theseus/src/api/pack.rs b/theseus/src/api/pack.rs index 83befd22f..7be9f1e1a 100644 --- a/theseus/src/api/pack.rs +++ b/theseus/src/api/pack.rs @@ -136,18 +136,26 @@ pub async fn install_pack_from_version_id( None }; - install_pack(file, icon, Some(version.project_id), Some(version.id)).await + install_pack( + file, + icon, + Some(project.title), + Some(version.project_id), + Some(version.id), + ) + .await } pub async fn install_pack_from_file(path: PathBuf) -> crate::Result { let file = fs::read(path).await?; - install_pack(bytes::Bytes::from(file), None, None, None).await + install_pack(bytes::Bytes::from(file), None, None, None, None).await } async fn install_pack( file: bytes::Bytes, icon: Option, + override_title: Option, project_id: Option, version_id: Option, ) -> crate::Result { @@ -192,7 +200,7 @@ async fn install_pack( let mut game_version = None; let mut mod_loader = None; let mut loader_version = None; - for (key, value) in pack.dependencies { + for (key, value) in &pack.dependencies { match key { PackDependency::Forge => { mod_loader = Some(ModLoader::Forge); @@ -219,13 +227,11 @@ async fn install_pack( .into()); }; - let pack_name = pack.name.clone(); - - let profile = crate::api::profile_create::profile_create( - pack.name, + let profile_raw = crate::api::profile_create::profile_create( + override_title.unwrap_or_else(|| pack.name.clone()), game_version.clone(), mod_loader.unwrap_or(ModLoader::Vanilla), - loader_version, + loader_version.cloned(), icon, Some(LinkedData { project_id: project_id.clone(), @@ -234,141 +240,167 @@ async fn install_pack( Some(true), ) .await?; - - let loading_bar = init_loading( - LoadingBarType::PackDownload { - pack_name, - pack_id: project_id, - pack_version: version_id, - }, - 100.0, - "Downloading modpack...", - ) - .await?; - - let num_files = pack.files.len(); - use futures::StreamExt; - loading_try_for_each_concurrent( - futures::stream::iter(pack.files.into_iter()) - .map(Ok::), - None, - Some(&loading_bar), - 80.0, - num_files, - None, - |project| { - let profile = profile.clone(); - async move { - //TODO: Future update: prompt user for optional files in a modpack - if let Some(env) = project.env { - if env - .get(&EnvType::Client) - .map(|x| x == &SideType::Unsupported) - .unwrap_or(false) - { - return Ok(()); - } - } - - let file = fetch_mirrors( - &project - .downloads - .iter() - .map(|x| &**x) - .collect::>(), - project.hashes.get(&PackFileHash::Sha1).map(|x| &**x), - &state.io_semaphore, - ) - .await?; - - let path = - std::path::Path::new(&project.path).components().next(); - if let Some(path) = path { - match path { - Component::CurDir | Component::Normal(_) => { - let path = profile.join(project.path); - write(&path, &file, &state.io_semaphore) - .await?; - } - _ => {} - }; - } - Ok(()) - } - }, - ) - .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.05, Some("Extracting overrides")).await?; - extract_overrides("overrides".to_string()).await?; - extract_overrides("client_overrides".to_string()).await?; - emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) + let profile = profile_raw.clone(); + let result = async { + let loading_bar = init_loading( + LoadingBarType::PackDownload { + pack_name: pack.name.clone(), + pack_id: project_id, + pack_version: version_id, + }, + 100.0, + "Downloading modpack...", + ) .await?; - super::profile::sync(&profile).await?; + let num_files = pack.files.len(); + use futures::StreamExt; + loading_try_for_each_concurrent( + futures::stream::iter(pack.files.into_iter()) + .map(Ok::), + None, + Some(&loading_bar), + 80.0, + num_files, + None, + |project| { + let profile = profile.clone(); + async move { + //TODO: Future update: prompt user for optional files in a modpack + if let Some(env) = project.env { + if env + .get(&EnvType::Client) + .map(|x| x == &SideType::Unsupported) + .unwrap_or(false) + { + return Ok(()); + } + } - if let Some(profile) = crate::api::profile::get(&profile).await? { - crate::launcher::install_minecraft(&profile, Some(loading_bar)) + let file = fetch_mirrors( + &project + .downloads + .iter() + .map(|x| &**x) + .collect::>(), + project + .hashes + .get(&PackFileHash::Sha1) + .map(|x| &**x), + &state.io_semaphore, + ) + .await?; + + let path = std::path::Path::new(&project.path) + .components() + .next(); + if let Some(path) = path { + match path { + Component::CurDir | Component::Normal(_) => { + let path = profile.join(project.path); + write(&path, &file, &state.io_semaphore) + .await?; + } + _ => {} + }; + } + Ok(()) + } + }, + ) + .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.05, Some("Extracting overrides")) .await?; - } else { + extract_overrides("overrides".to_string()).await?; + extract_overrides("client_overrides".to_string()).await?; emit_loading(&loading_bar, 0.1, Some("Done extacting overrides")) .await?; - } - Ok(profile) + if let Some(profile) = crate::api::profile::get(&profile).await? { + tokio::try_join!( + super::profile::sync(&profile.path), + crate::launcher::install_minecraft( + &profile, + Some(loading_bar) + ), + )?; + } else { + emit_loading( + &loading_bar, + 0.1, + Some("Done extacting overrides"), + ) + .await?; + } + + Ok::(profile) + } + .await; + + match result { + Ok(profile) => Ok(profile), + Err(err) => { + let _ = crate::api::profile::remove(&profile_raw).await; + + Err(err) + } + } } else { Err(crate::Error::from(crate::ErrorKind::InputError( "No pack manifest found in mrpack".to_string(), diff --git a/theseus/src/launcher/args.rs b/theseus/src/launcher/args.rs index 70ddcb6da..f9fceac93 100644 --- a/theseus/src/launcher/args.rs +++ b/theseus/src/launcher/args.rs @@ -136,6 +136,7 @@ pub fn get_jvm_arguments( parsed_arguments.push(arg); } } + parsed_arguments.push("-Dorg.lwjgl.util.Debug=true".to_string()); Ok(parsed_arguments) } diff --git a/theseus/src/launcher/download.rs b/theseus/src/launcher/download.rs index 9a8b457df..c3aa4c745 100644 --- a/theseus/src/launcher/download.rs +++ b/theseus/src/launcher/download.rs @@ -264,7 +264,7 @@ pub async fn download_libraries( library .natives .as_ref()? - .get(&Os::native())?, + .get(&Os::native_arch())?, library .downloads .as_ref()? diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index 8f23921c2..bf1c4cb40 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -4,6 +4,7 @@ use crate::event::LoadingBarType; use crate::{ process, state::{self as st, MinecraftChild}, + State, }; use daedalus as d; use dunce::canonicalize; @@ -55,7 +56,7 @@ pub async fn install_minecraft( profile: &Profile, existing_loading_bar: Option, ) -> crate::Result<()> { - let state = st::State::get().await?; + let state = State::get().await?; let instance_path = &canonicalize(&profile.path)?; let metadata = state.metadata.read().await; @@ -98,7 +99,6 @@ pub async fn install_minecraft( .await?; download::download_minecraft(&state, &version_info, loading_bar).await?; - st::State::sync().await?; let client_path = state .directories @@ -201,7 +201,7 @@ pub async fn install_minecraft( async { Ok(()) } }) .await?; - crate::api::profile::sync(&profile.path).await?; + State::sync().await?; Ok(()) } @@ -327,7 +327,7 @@ pub async fn launch_minecraft( let mut state_children = state.children.write().await; state_children .insert_process( - uuid::Uuid::new_v4(), + Uuid::new_v4(), instance_path.to_path_buf(), command, post_exit_hook, @@ -337,9 +337,11 @@ pub async fn launch_minecraft( fn clear_cargo_env_vals(command: &mut Command) -> &mut Command { for (key, _) in std::env::vars() { - if key.starts_with("CARGO") { - command.env_remove(key); - } + command.env_remove(key); + + // if key.starts_with("CARGO") { + // command.env_remove(key); + // } } command } diff --git a/theseus/src/state/metadata.rs b/theseus/src/state/metadata.rs index fdcc0b93f..557ef11dd 100644 --- a/theseus/src/state/metadata.rs +++ b/theseus/src/state/metadata.rs @@ -1,6 +1,7 @@ //! Theseus metadata use crate::data::DirectoryInfo; use crate::util::fetch::{read_json, write}; +use crate::State; use daedalus::{ minecraft::{fetch_version_manifest, VersionManifest as MinecraftManifest}, modded::{ @@ -96,7 +97,7 @@ impl Metadata { pub async fn update() { let res = async { let metadata_fetch = Metadata::fetch().await?; - let state = crate::State::get().await?; + let state = State::get().await?; let metadata_path = state.directories.caches_meta_dir().join("metadata.json"); diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index 297f2ceb4..bbff16bd8 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -164,7 +164,8 @@ impl Profile { let paths = self.get_profile_project_paths()?; let projects = crate::state::infer_data_from_files( - &[(self.clone(), paths)], + self.clone(), + paths, state.directories.caches_dir(), &state.io_semaphore, ) @@ -386,31 +387,28 @@ impl Profiles { } } - if !files.is_empty() { - let inferred = super::projects::infer_data_from_files( - &files, - state.directories.caches_dir(), - &state.io_semaphore, - ) - .await?; - let mut wipe_profiles = Vec::new(); - for (key, value) in inferred { - if let Some((profile, _)) = - files.iter().find(|(_, files)| files.contains(&key)) + future::try_join_all(files.into_iter().map( + |(profile, files)| async { + let profile_path = profile.path.clone(); + let inferred = super::projects::infer_data_from_files( + profile, + files, + state.directories.caches_dir(), + &state.io_semaphore, + ) + .await?; + + let mut new_profiles = state.profiles.write().await; + if let Some(profile) = new_profiles.0.get_mut(&profile_path) { - let mut new_profiles = state.profiles.write().await; - if let Some(profile) = - new_profiles.0.get_mut(&profile.path) - { - if !wipe_profiles.contains(&profile.path) { - profile.projects = HashMap::new(); - wipe_profiles.push(profile.path.clone()); - } - profile.projects.insert(key, value); - } + profile.projects = inferred; } - } - } + drop(new_profiles); + + Ok::<(), crate::Error>(()) + }, + )) + .await?; Ok::<(), crate::Error>(()) } diff --git a/theseus/src/state/projects.rs b/theseus/src/state/projects.rs index 85da2f26a..ffdadece9 100644 --- a/theseus/src/state/projects.rs +++ b/theseus/src/state/projects.rs @@ -1,7 +1,6 @@ //! Project management + inference use crate::config::MODRINTH_API_URL; -use crate::data::ModLoader; use crate::state::Profile; use crate::util::fetch::{fetch_json, write_cached_icon}; use async_zip::tokio::read::fs::ZipFileReader; @@ -250,36 +249,51 @@ async fn read_icon_from_file( } pub async fn infer_data_from_files( - paths: &[(Profile, Vec)], + profile: Profile, + paths: Vec, cache_dir: PathBuf, io_semaphore: &RwLock, ) -> crate::Result> { let mut file_path_hashes = HashMap::new(); // TODO: Make this concurrent and use progressive hashing to avoid loading each JAR in memory - for set in paths { - for path in &set.1 { - let mut file = tokio::fs::File::open(path.clone()).await?; + for path in paths { + let mut file = tokio::fs::File::open(path.clone()).await?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).await?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer).await?; - let hash = format!("{:x}", sha2::Sha512::digest(&buffer)); - file_path_hashes.insert(hash, path.clone()); - } + let hash = format!("{:x}", sha2::Sha512::digest(&buffer)); + file_path_hashes.insert(hash, path.clone()); } - let files: HashMap = fetch_json( - Method::POST, - &format!("{}version_files", MODRINTH_API_URL), - None, - Some(json!({ - "hashes": file_path_hashes.keys().collect::>(), - "algorithm": "sha512", - })), - io_semaphore, - ) - .await?; + let files_url = format!("{}version_files", MODRINTH_API_URL); + let updates_url = format!("{}version_files/update", MODRINTH_API_URL); + let (files, update_versions) = tokio::try_join!( + fetch_json::>( + Method::POST, + &files_url, + None, + Some(json!({ + "hashes": file_path_hashes.keys().collect::>(), + "algorithm": "sha512", + })), + io_semaphore, + ), + fetch_json::>( + Method::POST, + &updates_url, + None, + Some(json!({ + "hashes": file_path_hashes.keys().collect::>(), + "algorithm": "sha512", + "loaders": [profile.metadata.loader], + "game_versions": [profile.metadata.game_version] + })), + io_semaphore, + ) + )?; + let projects: Vec = fetch_json( Method::GET, &format!( @@ -297,6 +311,7 @@ pub async fn infer_data_from_files( io_semaphore, ) .await?; + let teams: Vec = fetch_json::< Vec>, >( @@ -317,26 +332,6 @@ pub async fn infer_data_from_files( .flatten() .collect(); - let mut update_versions: Vec = fetch_json( - Method::GET, - &format!( - "{}versions?ids={}", - MODRINTH_API_URL, - serde_json::to_string( - &projects - .iter() - .flat_map(|x| x.versions.clone()) - .collect::>() - )? - ), - None, - None, - io_semaphore, - ) - .await?; - - update_versions.sort_by(|a, b| b.date_published.cmp(&a.date_published)); - let mut return_projects = HashMap::new(); let mut further_analyze_projects: Vec<(String, PathBuf)> = Vec::new(); @@ -345,8 +340,6 @@ pub async fn infer_data_from_files( if let Some(project) = projects.iter().find(|x| version.project_id == x.id) { - let profile = paths.iter().find(|x| x.1.contains(&path)); - let file_name = path .file_name() .unwrap_or_default() @@ -356,7 +349,6 @@ pub async fn infer_data_from_files( return_projects.insert( path, Project { - sha512: hash, disabled: false, metadata: ProjectMetadata::Modrinth { project: Box::new(project.clone()), @@ -366,48 +358,25 @@ pub async fn infer_data_from_files( .filter(|x| x.team_id == project.team) .cloned() .collect::>(), - update_version: if let Some((profile, _)) = &profile + update_version: if let Some(val) = + update_versions.get(&hash) { - update_versions - .iter() - .find(|x| { - x.project_id == project.id - && x.game_versions.contains( - &profile.metadata.game_version, - ) - && if profile.metadata.loader - == ModLoader::Vanilla - { - true - } else { - x.loaders.contains( - &profile - .metadata - .loader - .as_api_str() - .to_string(), - ) - } - }) - .cloned() - .map(Box::new) + Some(Box::new(val.clone())) } else { None }, - incompatible: if let Some((profile, _)) = &profile { - !version.loaders.contains( - &profile - .metadata - .loader - .as_api_str() - .to_string(), - ) || version - .game_versions - .contains(&profile.metadata.game_version) - } else { - false - }, + + incompatible: !version.loaders.contains( + &profile + .metadata + .loader + .as_api_str() + .to_string(), + ) || version + .game_versions + .contains(&profile.metadata.game_version), }, + sha512: hash, file_name, }, ); diff --git a/theseus/src/util/platform.rs b/theseus/src/util/platform.rs index c41534548..54fbce776 100644 --- a/theseus/src/util/platform.rs +++ b/theseus/src/util/platform.rs @@ -6,10 +6,63 @@ use regex::Regex; pub trait OsExt { /// Get the OS of the current system fn native() -> Self; + + /// Gets the OS + Arch of the current system + fn native_arch() -> Self; } impl OsExt for Os { + fn native_arch() -> Self { + if std::env::consts::OS == "windows" { + if std::env::consts::ARCH == "aarch64" { + Os::WindowsArm64 + } else { + Os::Windows + } + } else if std::env::consts::OS == "linux" { + if std::env::consts::ARCH == "aarch64" { + Os::LinuxArm64 + } else if std::env::consts::ARCH == "arm" { + Os::LinuxArm32 + } else { + Os::Linux + } + } else if std::env::consts::OS == "macos" { + if std::env::consts::ARCH == "aarch64" { + Os::OsxArm64 + } else { + Os::Osx + } + } else { + Os::Unknown + } + } + fn native() -> Self { + if std::env::consts::OS == "windows" { + if std::env::consts::ARCH == "aarch64" { + Os::WindowsArm64 + } else { + Os::Windows + } + } else if std::env::consts::OS == "linux" { + if std::env::consts::ARCH == "aarch64" { + Os::LinuxArm64 + } else if std::env::consts::ARCH == "arm" { + Os::LinuxArm32 + } else { + Os::Linux + } + } else if std::env::consts::OS == "macos" { + if std::env::consts::ARCH == "aarch64" { + Os::OsxArm64 + } else { + Os::Osx + } + } else { + Os::Unknown + }; + match std::env::consts::OS { "windows" => Self::Windows, "macos" => Self::Osx, @@ -35,7 +88,7 @@ pub fn os_rule(rule: &OsRule) -> bool { } if let Some(name) = &rule.name { - rule_match &= &Os::native() == name; + rule_match &= &Os::native() == name || &Os::native_arch() == name; } if let Some(version) = &rule.version { @@ -49,8 +102,13 @@ pub fn os_rule(rule: &OsRule) -> bool { } pub fn classpath_separator() -> &'static str { - match Os::native() { - Os::Osx | Os::Linux | Os::Unknown => ":", - Os::Windows => ";", + match Os::native_arch() { + Os::Osx + | Os::OsxArm64 + | Os::Linux + | Os::LinuxArm32 + | Os::LinuxArm64 + | Os::Unknown => ":", + Os::Windows | Os::WindowsArm64 => ";", } } diff --git a/theseus_gui/src/App.vue b/theseus_gui/src/App.vue index f775f67db..be4124329 100644 --- a/theseus_gui/src/App.vue +++ b/theseus_gui/src/App.vue @@ -1,20 +1,14 @@ + + diff --git a/theseus_gui/src/components/ui/RunningAppBar.vue b/theseus_gui/src/components/ui/RunningAppBar.vue new file mode 100644 index 000000000..de98c43ef --- /dev/null +++ b/theseus_gui/src/components/ui/RunningAppBar.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/theseus_gui/src/helpers/process.js b/theseus_gui/src/helpers/process.js index 905c79d60..f7a1a11e4 100644 --- a/theseus_gui/src/helpers/process.js +++ b/theseus_gui/src/helpers/process.js @@ -31,8 +31,8 @@ export async function get_all_running_uuids() { /// Gets all running process IDs with a given profile path /// Returns [u32] -export async function get_uuids_by_profile_path(profile_path) { - return await invoke('process_get_uuids_by_profile_path', { profile_path }) +export async function get_uuids_by_profile_path(profilePath) { + return await invoke('process_get_uuids_by_profile_path', { profilePath }) } /// Gets all running process IDs with a given profile path @@ -43,8 +43,8 @@ export async function get_all_running_profile_paths(profile_path) { /// Gets all running process IDs with a given profile path /// Returns [u32] -export async function get_all_running_profiles(profile_path) { - return await invoke('process_get_all_running_profiles', { profile_path }) +export async function get_all_running_profiles() { + return await invoke('process_get_all_running_profiles') } /// Gets process stderr by UUID diff --git a/theseus_gui/src/pages/Browse.vue b/theseus_gui/src/pages/Browse.vue index 8c324883c..1cb5a94d1 100644 --- a/theseus_gui/src/pages/Browse.vue +++ b/theseus_gui/src/pages/Browse.vue @@ -17,9 +17,14 @@ import { } from 'omorphia' import Multiselect from 'vue-multiselect' import { useSearch } from '@/store/state' +import { useBreadcrumbs } from '@/store/breadcrumbs' import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags' +import { useRoute } from 'vue-router' const searchStore = useSearch() +const breadcrumbs = useBreadcrumbs() +const route = useRoute() +breadcrumbs.setContext({ name: 'Browse', link: route.path }) const showSnapshots = ref(false) const loading = ref(true) diff --git a/theseus_gui/src/pages/Index.vue b/theseus_gui/src/pages/Index.vue index 9cd11e861..af4f812f2 100644 --- a/theseus_gui/src/pages/Index.vue +++ b/theseus_gui/src/pages/Index.vue @@ -2,9 +2,16 @@ import RowDisplay from '@/components/RowDisplay.vue' import { shallowRef } from 'vue' import { list } from '@/helpers/profile.js' +import { useRoute } from 'vue-router' +import { useBreadcrumbs } from '@/store/breadcrumbs' + +const route = useRoute() +const breadcrumbs = useBreadcrumbs() const profiles = await list() const recentInstances = shallowRef(Object.values(profiles)) + +breadcrumbs.setRootContext({ name: 'Home', link: route.path })