You've already forked AstralRinth
forked from didirus/AstralRinth
Onboarding (#132)
* Initial onboarding * Update OnboardingModal.vue * Add finish * Animation * Automatic opening * Move onboarding icon to outside of main appbar * Run lint * run fmt * mostly finish * Finish onboarding * fix onboarding bug + linux build * fix build again * Add back window shadows --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Jai A <jai@modrinth.com>
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -4567,7 +4567,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
"async_zip",
|
"async_zip",
|
||||||
@@ -4608,7 +4608,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_cli"
|
name = "theseus_cli"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@@ -4660,6 +4660,7 @@ dependencies = [
|
|||||||
"tracing-subscriber 0.2.25",
|
"tracing-subscriber 0.2.25",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"window-shadows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4672,7 +4673,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_playground"
|
name = "theseus_playground"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"daedalus",
|
"daedalus",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -5484,6 +5485,18 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "window-shadows"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29d30320647cfc3dc45554c8ad825b84831def81f967a2f7589931328ff9b16d"
|
||||||
|
dependencies = [
|
||||||
|
"cocoa",
|
||||||
|
"objc",
|
||||||
|
"raw-window-handle",
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.37.0"
|
version = "0.37.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ pub const JAVA_18PLUS_KEY: &str = "JAVA_18PLUS";
|
|||||||
// Autodetect JavaSettings default
|
// Autodetect JavaSettings default
|
||||||
// Make a guess for what the default Java global settings should be
|
// Make a guess for what the default Java global settings should be
|
||||||
pub async fn autodetect_java_globals() -> crate::Result<JavaGlobals> {
|
pub async fn autodetect_java_globals() -> crate::Result<JavaGlobals> {
|
||||||
let mut java_8 = find_java8_jres().await?;
|
let mut java_8 = find_filtered_jres("1.8").await?;
|
||||||
let mut java_17 = find_java17_jres().await?;
|
let mut java_17 = find_filtered_jres("1.17").await?;
|
||||||
let mut java_18plus = find_java18plus_jres().await?;
|
let mut java_18plus = find_filtered_jres("1.18").await?;
|
||||||
|
|
||||||
// Simply select last one found for initial guess
|
// Simply select last one found for initial guess
|
||||||
let mut java_globals = JavaGlobals::new();
|
let mut java_globals = JavaGlobals::new();
|
||||||
@@ -37,11 +37,13 @@ pub async fn autodetect_java_globals() -> crate::Result<JavaGlobals> {
|
|||||||
Ok(java_globals)
|
Ok(java_globals)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches for jres on the system that are 1.18 or higher
|
// Searches for jres on the system given a java version (ex: 1.8, 1.17, 1.18)
|
||||||
pub async fn find_java18plus_jres() -> crate::Result<Vec<JavaVersion>> {
|
pub async fn find_filtered_jres(
|
||||||
let version = extract_java_majorminor_version("1.18")?;
|
version: &str,
|
||||||
|
) -> crate::Result<Vec<JavaVersion>> {
|
||||||
|
let version = extract_java_majorminor_version(version)?;
|
||||||
let jres = jre::get_all_jre().await?;
|
let jres = jre::get_all_jre().await?;
|
||||||
// Filter out JREs that are not 1.17 or higher
|
|
||||||
Ok(jres
|
Ok(jres
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|jre| {
|
.filter(|jre| {
|
||||||
@@ -55,44 +57,6 @@ pub async fn find_java18plus_jres() -> crate::Result<Vec<JavaVersion>> {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches for jres on the system that are 1.8 exactly
|
|
||||||
pub async fn find_java8_jres() -> crate::Result<Vec<JavaVersion>> {
|
|
||||||
let version = extract_java_majorminor_version("1.8")?;
|
|
||||||
let jres = jre::get_all_jre().await?;
|
|
||||||
|
|
||||||
// Filter out JREs that are not 1.8
|
|
||||||
Ok(jres
|
|
||||||
.into_iter()
|
|
||||||
.filter(|jre| {
|
|
||||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
|
||||||
if let Ok(jre_version) = jre_version {
|
|
||||||
jre_version == version
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Searches for jres on the system that are 1.17 exactly
|
|
||||||
pub async fn find_java17_jres() -> crate::Result<Vec<JavaVersion>> {
|
|
||||||
let version = extract_java_majorminor_version("1.17")?;
|
|
||||||
let jres = jre::get_all_jre().await?;
|
|
||||||
|
|
||||||
// Filter out JREs that are not 1.8
|
|
||||||
Ok(jres
|
|
||||||
.into_iter()
|
|
||||||
.filter(|jre| {
|
|
||||||
let jre_version = extract_java_majorminor_version(&jre.version);
|
|
||||||
if let Ok(jre_version) = jre_version {
|
|
||||||
jre_version == version
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[theseus_macros::debug_pin]
|
#[theseus_macros::debug_pin]
|
||||||
pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
@@ -157,16 +121,31 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
|
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
|
||||||
Ok(path
|
let mut base_path = path.join(
|
||||||
.join(
|
download
|
||||||
download
|
.name
|
||||||
.name
|
.file_stem()
|
||||||
.file_stem()
|
.unwrap_or_default()
|
||||||
.unwrap_or_default()
|
.to_string_lossy()
|
||||||
.to_string_lossy()
|
.to_string(),
|
||||||
.to_string(),
|
);
|
||||||
)
|
|
||||||
.join(format!("zulu-{}.jre/Contents/Home/bin/java", java_version)))
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
base_path = base_path
|
||||||
|
.join(format!("zulu-{}.jre", java_version))
|
||||||
|
.join("Contents")
|
||||||
|
.join("Home")
|
||||||
|
.join("bin")
|
||||||
|
.join("java")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
base_path = base_path.join("bin").join(jre::JAVA_BIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(base_path)
|
||||||
} else {
|
} else {
|
||||||
Err(crate::ErrorKind::LauncherError(format!(
|
Err(crate::ErrorKind::LauncherError(format!(
|
||||||
"No Java Version found for Java version {}, OS {}, and Architecture {}",
|
"No Java Version found for Java version {}, OS {}, and Architecture {}",
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ pub struct Settings {
|
|||||||
pub opt_out_analytics: bool,
|
pub opt_out_analytics: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub advanced_rendering: bool,
|
pub advanced_rendering: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub onboarded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Settings {
|
impl Default for Settings {
|
||||||
@@ -52,6 +54,7 @@ impl Default for Settings {
|
|||||||
developer_mode: false,
|
developer_mode: false,
|
||||||
opt_out_analytics: false,
|
opt_out_analytics: false,
|
||||||
advanced_rendering: true,
|
advanced_rendering: true,
|
||||||
|
onboarded: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
let mut jre_paths = HashSet::new();
|
let mut jre_paths = HashSet::new();
|
||||||
|
|
||||||
// Add JRES directly on PATH
|
// Add JRES directly on PATH
|
||||||
jre_paths.extend(get_all_jre_path().await?);
|
jre_paths.extend(get_all_jre_path().await);
|
||||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||||
if let Ok(java_home) = env::var("JAVA_HOME") {
|
if let Ok(java_home) = env::var("JAVA_HOME") {
|
||||||
jre_paths.insert(PathBuf::from(java_home));
|
jre_paths.insert(PathBuf::from(java_home));
|
||||||
@@ -47,8 +47,10 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
for java_path in java_paths {
|
for java_path in java_paths {
|
||||||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
||||||
for java_subpath in java_subpaths {
|
for java_subpath in java_subpaths {
|
||||||
let path = java_subpath?.path();
|
if let Ok(java_subpath) = java_subpath {
|
||||||
jre_paths.insert(path.join("bin"));
|
let path = java_subpath.path();
|
||||||
|
jre_paths.insert(path.join("bin"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,18 +70,18 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE)
|
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE)
|
||||||
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_32KEY)
|
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_32KEY)
|
||||||
{
|
{
|
||||||
jre_paths.extend(get_paths_from_jre_winregkey(jre_key)?);
|
jre_paths.extend(get_paths_from_jre_winregkey(jre_key));
|
||||||
}
|
}
|
||||||
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE)
|
if let Ok(jre_key) = RegKey::predef(HKEY_LOCAL_MACHINE)
|
||||||
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY)
|
.open_subkey_with_flags(key, KEY_READ | KEY_WOW64_64KEY)
|
||||||
{
|
{
|
||||||
jre_paths.extend(get_paths_from_jre_winregkey(jre_key)?);
|
jre_paths.extend(get_paths_from_jre_winregkey(jre_key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JRE versions from potential paths concurrently
|
// Get JRE versions from potential paths concurrently
|
||||||
let j = check_java_at_filepaths(jre_paths)
|
let j = check_java_at_filepaths(jre_paths)
|
||||||
.await?
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
Ok(j)
|
Ok(j)
|
||||||
@@ -88,27 +90,26 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
// Gets paths rather than search directly as RegKeys should not be passed asynchronously (do not impl Send)
|
// Gets paths rather than search directly as RegKeys should not be passed asynchronously (do not impl Send)
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub fn get_paths_from_jre_winregkey(
|
pub fn get_paths_from_jre_winregkey(jre_key: RegKey) -> HashSet<PathBuf> {
|
||||||
jre_key: RegKey,
|
|
||||||
) -> Result<HashSet<PathBuf>, JREError> {
|
|
||||||
let mut jre_paths = HashSet::new();
|
let mut jre_paths = HashSet::new();
|
||||||
|
|
||||||
for subkey in jre_key.enum_keys() {
|
for subkey in jre_key.enum_keys() {
|
||||||
let subkey = subkey?;
|
if let Ok(subkey) = subkey {
|
||||||
let subkey = jre_key.open_subkey(subkey)?;
|
if let Ok(subkey) = jre_key.open_subkey(subkey) {
|
||||||
|
let subkey_value_names =
|
||||||
|
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
||||||
|
|
||||||
let subkey_value_names =
|
for subkey_value in subkey_value_names {
|
||||||
[r"JavaHome", r"InstallationPath", r"\\hotspot\\MSI"];
|
let path: Result<String, std::io::Error> =
|
||||||
|
subkey.get_value(subkey_value);
|
||||||
|
let Ok(path) = path else {continue};
|
||||||
|
|
||||||
for subkey_value in subkey_value_names {
|
jre_paths.insert(PathBuf::from(path).join("bin"));
|
||||||
let path: Result<String, std::io::Error> =
|
}
|
||||||
subkey.get_value(subkey_value);
|
}
|
||||||
let Ok(path) = path else {continue};
|
|
||||||
|
|
||||||
jre_paths.insert(PathBuf::from(path).join("bin"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(jre_paths)
|
jre_paths
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entrypoint function (Mac)
|
// Entrypoint function (Mac)
|
||||||
@@ -120,7 +121,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
let mut jre_paths = HashSet::new();
|
let mut jre_paths = HashSet::new();
|
||||||
|
|
||||||
// Add JREs directly on PATH
|
// Add JREs directly on PATH
|
||||||
jre_paths.extend(get_all_jre_path().await?);
|
jre_paths.extend(get_all_jre_path().await);
|
||||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||||
|
|
||||||
// Hard paths for locations for commonly installed .exes
|
// Hard paths for locations for commonly installed .exes
|
||||||
@@ -134,16 +135,18 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
}
|
}
|
||||||
// Iterate over JavaVirtualMachines/(something)/Contents/Home/bin
|
// Iterate over JavaVirtualMachines/(something)/Contents/Home/bin
|
||||||
let base_path = PathBuf::from("/Library/Java/JavaVirtualMachines/");
|
let base_path = PathBuf::from("/Library/Java/JavaVirtualMachines/");
|
||||||
if base_path.is_dir() {
|
if let Ok(dir) = std::fs::read_dir(base_path) {
|
||||||
for entry in std::fs::read_dir(base_path)? {
|
for entry in dir {
|
||||||
let entry = entry?.path().join("Contents/Home/bin");
|
if let Ok(entry) = entry {
|
||||||
jre_paths.insert(entry);
|
let entry = entry.path().join("Contents/Home/bin");
|
||||||
|
jre_paths.insert(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JRE versions from potential paths concurrently
|
// Get JRE versions from potential paths concurrently
|
||||||
let j = check_java_at_filepaths(jre_paths)
|
let j = check_java_at_filepaths(jre_paths)
|
||||||
.await?
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
Ok(j)
|
Ok(j)
|
||||||
@@ -158,7 +161,7 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
let mut jre_paths = HashSet::new();
|
let mut jre_paths = HashSet::new();
|
||||||
|
|
||||||
// Add JREs directly on PATH
|
// Add JREs directly on PATH
|
||||||
jre_paths.extend(get_all_jre_path().await?);
|
jre_paths.extend(get_all_jre_path().await);
|
||||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||||
|
|
||||||
// Hard paths for locations for commonly installed locations
|
// Hard paths for locations for commonly installed locations
|
||||||
@@ -174,18 +177,20 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
|||||||
let path = PathBuf::from(path);
|
let path = PathBuf::from(path);
|
||||||
jre_paths.insert(PathBuf::from(&path).join("jre").join("bin"));
|
jre_paths.insert(PathBuf::from(&path).join("jre").join("bin"));
|
||||||
jre_paths.insert(PathBuf::from(&path).join("bin"));
|
jre_paths.insert(PathBuf::from(&path).join("bin"));
|
||||||
if path.is_dir() {
|
if let Ok(dir) = std::fs::read_dir(path) {
|
||||||
for entry in std::fs::read_dir(&path)? {
|
for entry in dir {
|
||||||
let entry_path = entry?.path();
|
if let Ok(entry) = entry {
|
||||||
jre_paths.insert(entry_path.join("jre").join("bin"));
|
let entry_path = entry.path();
|
||||||
jre_paths.insert(entry_path.join("bin"));
|
jre_paths.insert(entry_path.join("jre").join("bin"));
|
||||||
|
jre_paths.insert(entry_path.join("bin"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get JRE versions from potential paths concurrently
|
// Get JRE versions from potential paths concurrently
|
||||||
let j = check_java_at_filepaths(jre_paths)
|
let j = check_java_at_filepaths(jre_paths)
|
||||||
.await?
|
.await
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
Ok(j)
|
Ok(j)
|
||||||
@@ -203,13 +208,25 @@ async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
|||||||
let base_path = state.directories.java_versions_dir();
|
let base_path = state.directories.java_versions_dir();
|
||||||
|
|
||||||
if base_path.is_dir() {
|
if base_path.is_dir() {
|
||||||
for entry in std::fs::read_dir(base_path)? {
|
if let Ok(dir) = std::fs::read_dir(base_path) {
|
||||||
let entry = entry?;
|
for entry in dir {
|
||||||
let file_path = entry.path().join("bin");
|
if let Ok(entry) = entry {
|
||||||
let contents = std::fs::read_to_string(file_path)?;
|
let file_path = entry.path().join("bin");
|
||||||
|
|
||||||
let entry = entry.path().join(contents);
|
if let Ok(contents) =
|
||||||
jre_paths.insert(entry);
|
std::fs::read_to_string(file_path.clone())
|
||||||
|
{
|
||||||
|
let entry = entry.path().join(contents);
|
||||||
|
jre_paths.insert(entry);
|
||||||
|
} else {
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
let file_path = file_path.join(JAVA_BIN);
|
||||||
|
jre_paths.insert(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,26 +237,27 @@ async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
|||||||
|
|
||||||
// Gets all JREs from the PATH env variable
|
// Gets all JREs from the PATH env variable
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
async fn get_all_jre_path() -> Result<HashSet<PathBuf>, JREError> {
|
async fn get_all_jre_path() -> HashSet<PathBuf> {
|
||||||
// Iterate over values in PATH variable, where accessible JREs are referenced
|
// Iterate over values in PATH variable, where accessible JREs are referenced
|
||||||
let paths = env::var("PATH")?;
|
let paths =
|
||||||
Ok(env::split_paths(&paths).collect())
|
env::var("PATH").map(|x| env::split_paths(&x).collect::<HashSet<_>>());
|
||||||
|
paths.unwrap_or_else(|_| HashSet::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const JAVA_BIN: &str = "javaw.exe";
|
pub const JAVA_BIN: &str = "javaw.exe";
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const JAVA_BIN: &str = "java";
|
pub const JAVA_BIN: &str = "java";
|
||||||
|
|
||||||
// For each example filepath in 'paths', perform check_java_at_filepath, checking each one concurrently
|
// For each example filepath in 'paths', perform check_java_at_filepath, checking each one concurrently
|
||||||
// and returning a JavaVersion for every valid path that points to a java bin
|
// and returning a JavaVersion for every valid path that points to a java bin
|
||||||
#[tracing::instrument]
|
#[tracing::instrument]
|
||||||
pub async fn check_java_at_filepaths(
|
pub async fn check_java_at_filepaths(
|
||||||
paths: HashSet<PathBuf>,
|
paths: HashSet<PathBuf>,
|
||||||
) -> Result<HashSet<JavaVersion>, JREError> {
|
) -> HashSet<JavaVersion> {
|
||||||
let jres = stream::iter(paths.into_iter())
|
let jres = stream::iter(paths.into_iter())
|
||||||
.map(|p: PathBuf| {
|
.map(|p: PathBuf| {
|
||||||
tokio::task::spawn(async move { check_java_at_filepath(&p).await })
|
tokio::task::spawn(async move { check_java_at_filepath(&p).await })
|
||||||
@@ -248,8 +266,7 @@ pub async fn check_java_at_filepaths(
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let jres: Result<Vec<_>, JoinError> = jres.into_iter().collect();
|
jres.into_iter().flat_map(|x| x.ok()).flatten().collect()
|
||||||
Ok(jres?.into_iter().flatten().collect())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
// For example filepath 'path', attempt to resolve it and get a Java version at this path
|
||||||
@@ -380,19 +397,3 @@ pub enum JREError {
|
|||||||
#[error("Error getting launcher sttae")]
|
#[error("Error getting launcher sttae")]
|
||||||
StateError,
|
StateError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::extract_java_majorminor_version;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn java_version_parsing() {
|
|
||||||
assert_eq!(extract_java_majorminor_version("1.8").unwrap(), (1, 8));
|
|
||||||
assert_eq!(extract_java_majorminor_version("17.0.6").unwrap(), (1, 17));
|
|
||||||
assert_eq!(extract_java_majorminor_version("20").unwrap(), (1, 20));
|
|
||||||
assert_eq!(
|
|
||||||
extract_java_majorminor_version("1.8.0_361").unwrap(),
|
|
||||||
(1, 8)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_cli"
|
name = "theseus_cli"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,9 @@
|
|||||||
"floating-vue": "^2.0.0-beta.20",
|
"floating-vue": "^2.0.0-beta.20",
|
||||||
"mixpanel-browser": "^2.47.0",
|
"mixpanel-browser": "^2.47.0",
|
||||||
"ofetch": "^1.0.1",
|
"ofetch": "^1.0.1",
|
||||||
"omorphia": "^0.4.28",
|
"omorphia": "^0.4.31",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
|
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
|
||||||
"vite-svg-loader": "^4.0.0",
|
"vite-svg-loader": "^4.0.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-multiselect": "^3.0.0-beta.2",
|
"vue-multiselect": "^3.0.0-beta.2",
|
||||||
|
|||||||
19
theseus_gui/pnpm-lock.yaml
generated
19
theseus_gui/pnpm-lock.yaml
generated
@@ -17,11 +17,14 @@ dependencies:
|
|||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
omorphia:
|
omorphia:
|
||||||
specifier: ^0.4.28
|
specifier: ^0.4.31
|
||||||
version: 0.4.28
|
version: 0.4.31
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3(vue@3.3.4)
|
version: 2.1.3(vue@3.3.4)
|
||||||
|
tauri-plugin-window-state-api:
|
||||||
|
specifier: github:tauri-apps/tauri-plugin-window-state#v1
|
||||||
|
version: github.com/tauri-apps/tauri-plugin-window-state/56fd671f8d5ac2d8b826a358af486f220a125c3d
|
||||||
vite-svg-loader:
|
vite-svg-loader:
|
||||||
specifier: ^4.0.0
|
specifier: ^4.0.0
|
||||||
version: 4.0.0
|
version: 4.0.0
|
||||||
@@ -1333,8 +1336,8 @@ packages:
|
|||||||
ufo: 1.1.2
|
ufo: 1.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/omorphia@0.4.28:
|
/omorphia@0.4.31:
|
||||||
resolution: {integrity: sha512-ZTUgBD3ZL+aymS7u5pLaPo8I5FUI8fkoz2dZLtAS5ksGRI5wrWkwIi/kxjpC95A2oDxNQvZaylfwUTK3Z6a2Sw==}
|
resolution: {integrity: sha512-xeb9bD42VFRDKCkKz678hBYCIS//Atd4/hx6/YmboJLMEIjIJfS2Ocf9G53G52XkfS4DWs9CIzKz71NDh86kxQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dayjs: 1.11.7
|
dayjs: 1.11.7
|
||||||
floating-vue: 2.0.0-beta.20(vue@3.3.4)
|
floating-vue: 2.0.0-beta.20(vue@3.3.4)
|
||||||
@@ -1787,3 +1790,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
github.com/tauri-apps/tauri-plugin-window-state/56fd671f8d5ac2d8b826a358af486f220a125c3d:
|
||||||
|
resolution: {tarball: https://codeload.github.com/tauri-apps/tauri-plugin-window-state/tar.gz/56fd671f8d5ac2d8b826a358af486f220a125c3d}
|
||||||
|
name: tauri-plugin-window-state-api
|
||||||
|
version: 0.0.0
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 1.3.0
|
||||||
|
dev: false
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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"] }
|
||||||
tauri = { version = "1.3", features = ["devtools", "dialog", "dialog-open", "macos-private-api", "os-all", "protocol-asset", "shell-open", "updater", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-set-decorations", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
tauri = { version = "1.3", features = ["app-all", "devtools", "dialog", "dialog-open", "macos-private-api", "os-all", "protocol-asset", "shell-open", "updater", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-set-decorations", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||||
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -39,6 +39,8 @@ tracing-error = "0.1"
|
|||||||
sentry = "0.30"
|
sentry = "0.30"
|
||||||
sentry-rust-minidump = "0.5"
|
sentry-rust-minidump = "0.5"
|
||||||
|
|
||||||
|
window-shadows = "0.2.1"
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
cocoa = "0.24.1"
|
cocoa = "0.24.1"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
|||||||
@@ -10,22 +10,22 @@ pub async fn jre_get_all_jre() -> Result<Vec<JavaVersion>> {
|
|||||||
Ok(jre::get_all_jre().await?)
|
Ok(jre::get_all_jre().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the isntallation of Java 7, if it exists
|
// Finds the installation of Java 8, if it exists
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn jre_find_jre_8_jres() -> Result<Vec<JavaVersion>> {
|
pub async fn jre_find_jre_8_jres() -> Result<Vec<JavaVersion>> {
|
||||||
Ok(jre::find_java8_jres().await?)
|
Ok(jre::find_filtered_jres("1.8").await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finds the installation of Java 17, if it exists
|
// finds the installation of Java 17, if it exists
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn jre_find_jre_17_jres() -> Result<Vec<JavaVersion>> {
|
pub async fn jre_find_jre_17_jres() -> Result<Vec<JavaVersion>> {
|
||||||
Ok(jre::find_java17_jres().await?)
|
Ok(jre::find_filtered_jres("1.17").await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finds the highest version of Java 18+, if it exists
|
// Finds the highest version of Java 18+, if it exists
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> {
|
pub async fn jre_find_jre_18plus_jres() -> Result<Vec<JavaVersion>> {
|
||||||
Ok(jre::find_java18plus_jres().await?)
|
Ok(jre::find_filtered_jres("1.18").await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Autodetect Java globals, by searching the users computer.
|
// Autodetect Java globals, by searching the users computer.
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
|
|
||||||
use theseus::prelude::*;
|
use theseus::prelude::*;
|
||||||
|
|
||||||
use tauri::{Manager, WindowEvent};
|
use tauri::Manager;
|
||||||
|
|
||||||
|
use window_shadows::set_shadow;
|
||||||
|
|
||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
@@ -78,13 +81,21 @@ fn main() {
|
|||||||
{
|
{
|
||||||
builder = builder.setup(|app| {
|
builder = builder.setup(|app| {
|
||||||
let win = app.get_window("main").unwrap();
|
let win = app.get_window("main").unwrap();
|
||||||
win.set_decorations(false);
|
win.set_decorations(false).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder = builder.setup(|app| {
|
||||||
|
let win = app.get_window("main").unwrap();
|
||||||
|
set_shadow(&win, true).unwrap();
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
|
use tauri::WindowEvent;
|
||||||
|
|
||||||
builder = builder
|
builder = builder
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
use api::window_ext::WindowExt;
|
use api::window_ext::WindowExt;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Modrinth App",
|
"productName": "Modrinth App",
|
||||||
"version": "0.0.1"
|
"version": "0.2.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@@ -40,6 +40,9 @@
|
|||||||
},
|
},
|
||||||
"os": {
|
"os": {
|
||||||
"all": true
|
"all": true
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"all": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"macOSPrivateApi": true,
|
"macOSPrivateApi": true,
|
||||||
@@ -72,7 +75,7 @@
|
|||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": null,
|
"certificateThumbprint": null,
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": ""
|
"timestampUrl": "http://timestamp.digicert.com"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
@@ -92,7 +95,7 @@
|
|||||||
"height": 650,
|
"height": 650,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"title": "Modrinth App",
|
"title": "Modrinth App",
|
||||||
"width": 1140,
|
"width": 1280,
|
||||||
"minHeight": 630,
|
"minHeight": 630,
|
||||||
"minWidth": 1100
|
"minWidth": 1100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { handleError, onMounted, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { RouterView, RouterLink, useRouter } from 'vue-router'
|
import { RouterView, RouterLink, useRouter } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
@@ -26,35 +26,20 @@ import { type } from '@tauri-apps/api/os'
|
|||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
import { isDev } from '@/helpers/utils.js'
|
import { isDev } from '@/helpers/utils.js'
|
||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
|
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||||
|
import OnboardingModal from '@/components/OnboardingModal.vue'
|
||||||
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
onMounted(async () => {
|
|
||||||
const { settings, collapsed_navigation } = await get().catch(handleError)
|
|
||||||
themeStore.setThemeState(settings)
|
|
||||||
themeStore.collapsedNavigation = collapsed_navigation
|
|
||||||
|
|
||||||
await warning_listener((e) =>
|
|
||||||
notificationsWrapper.value.addNotification({
|
|
||||||
title: 'Warning',
|
|
||||||
text: e.message,
|
|
||||||
type: 'warn',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
if ((await type()) === 'Darwin') {
|
|
||||||
document.getElementsByTagName('html')[0].classList.add('mac')
|
|
||||||
} else {
|
|
||||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initialize: async () => {
|
initialize: async () => {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering } = await get()
|
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering, onboarded } =
|
||||||
|
await get()
|
||||||
const dev = await isDev()
|
const dev = await isDev()
|
||||||
|
const version = await getVersion()
|
||||||
|
|
||||||
themeStore.setThemeState(theme)
|
themeStore.setThemeState(theme)
|
||||||
themeStore.collapsedNavigation = collapsed_navigation
|
themeStore.collapsedNavigation = collapsed_navigation
|
||||||
@@ -64,10 +49,16 @@ defineExpose({
|
|||||||
if (opt_out_analytics) {
|
if (opt_out_analytics) {
|
||||||
mixpanel.opt_out_tracking()
|
mixpanel.opt_out_tracking()
|
||||||
}
|
}
|
||||||
mixpanel.track('Launched')
|
mixpanel.track('Launched', { version, dev, onboarded })
|
||||||
|
|
||||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||||
|
|
||||||
|
if ((await type()) === 'Darwin') {
|
||||||
|
document.getElementsByTagName('html')[0].classList.add('mac')
|
||||||
|
} else {
|
||||||
|
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||||
|
}
|
||||||
|
|
||||||
await warning_listener((e) =>
|
await warning_listener((e) =>
|
||||||
notificationsWrapper.value.addNotification({
|
notificationsWrapper.value.addNotification({
|
||||||
title: 'Warning',
|
title: 'Warning',
|
||||||
@@ -119,15 +110,23 @@ document.querySelector('body').addEventListener('click', function (e) {
|
|||||||
target = target.parentElement
|
target = target.parentElement
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const accounts = ref(null)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SplashScreen v-if="isLoading" app-loading />
|
<SplashScreen v-if="isLoading" app-loading />
|
||||||
<div v-else class="container">
|
<div v-else class="container">
|
||||||
|
<suspense>
|
||||||
|
<OnboardingModal ref="testModal" :accounts="accounts" />
|
||||||
|
</suspense>
|
||||||
<div class="nav-container" :class="{ expanded: !themeStore.collapsedNavigation }">
|
<div class="nav-container" :class="{ expanded: !themeStore.collapsedNavigation }">
|
||||||
<div class="nav-section">
|
<div class="nav-section">
|
||||||
<suspense>
|
<suspense>
|
||||||
<AccountsCard ref="accounts" :expanded="!themeStore.collapsedNavigation" />
|
<AccountsCard
|
||||||
|
ref="accounts"
|
||||||
|
:mode="themeStore.collapsedNavigation ? 'small' : 'expanded'"
|
||||||
|
/>
|
||||||
</suspense>
|
</suspense>
|
||||||
<div class="pages-list">
|
<div class="pages-list">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
@@ -215,7 +214,16 @@ document.querySelector('body').addEventListener('click', function (e) {
|
|||||||
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
||||||
<MaximizeIcon />
|
<MaximizeIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<Button class="titlebar-button close" icon-only @click="() => appWindow.close()">
|
<Button
|
||||||
|
class="titlebar-button close"
|
||||||
|
icon-only
|
||||||
|
@click="
|
||||||
|
() => {
|
||||||
|
saveWindowState(StateFlags.ALL)
|
||||||
|
appWindow.close()
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
@@ -250,10 +258,11 @@ document.querySelector('body').addEventListener('click', function (e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.window-controls {
|
.window-controls {
|
||||||
|
z-index: 20;
|
||||||
display: none;
|
display: none;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0;
|
gap: 0.25rem;
|
||||||
|
|
||||||
.titlebar-button {
|
.titlebar-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
BIN
theseus_gui/src/assets/external/default.png
vendored
BIN
theseus_gui/src/assets/external/default.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
1
theseus_gui/src/assets/external/index.js
vendored
1
theseus_gui/src/assets/external/index.js
vendored
@@ -4,4 +4,3 @@ export { default as KoFiIcon } from './kofi.svg'
|
|||||||
export { default as PatreonIcon } from './patreon.svg'
|
export { default as PatreonIcon } from './patreon.svg'
|
||||||
export { default as PaypalIcon } from './paypal.svg'
|
export { default as PaypalIcon } from './paypal.svg'
|
||||||
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
export { default as OpenCollectiveIcon } from './opencollective.svg'
|
||||||
export { default as Default } from './default.png'
|
|
||||||
|
|||||||
336
theseus_gui/src/components/OnboardingModal.vue
Normal file
336
theseus_gui/src/components/OnboardingModal.vue
Normal file
File diff suppressed because one or more lines are too long
@@ -1,12 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
|
v-if="mode !== 'isolated'"
|
||||||
ref="button"
|
ref="button"
|
||||||
class="button-base avatar-button"
|
class="button-base avatar-button"
|
||||||
:class="{ expanded: expanded }"
|
:class="{ expanded: mode === 'expanded' }"
|
||||||
@click="toggle()"
|
@click="showCard = !showCard"
|
||||||
>
|
>
|
||||||
<Avatar :size="expanded ? 'xs' : 'sm'" :src="selectedAccount?.profile_picture ?? ''" />
|
<Avatar
|
||||||
<div v-show="expanded" class="avatar-text">
|
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
||||||
|
:src="selectedAccount ? `https://mc-heads.net/avatar/${selectedAccount.id}/128` : ''"
|
||||||
|
/>
|
||||||
|
<div v-show="mode === 'expanded'" class="avatar-text">
|
||||||
<div class="text no-select">
|
<div class="text no-select">
|
||||||
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
||||||
</div>
|
</div>
|
||||||
@@ -17,9 +21,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<Card v-if="showCard" ref="card" class="account-card" :class="{ expanded: expanded }">
|
<Card
|
||||||
|
v-if="showCard || mode === 'isolated'"
|
||||||
|
ref="card"
|
||||||
|
class="account-card"
|
||||||
|
:class="{ expanded: mode === 'expanded', isolated: mode === 'isolated' }"
|
||||||
|
>
|
||||||
<div v-if="selectedAccount" class="selected account">
|
<div v-if="selectedAccount" class="selected account">
|
||||||
<Avatar size="xs" :src="selectedAccount.profile_picture" />
|
<Avatar size="xs" :src="`https://mc-heads.net/avatar/${selectedAccount.id}/128`" />
|
||||||
<div>
|
<div>
|
||||||
<h4>{{ selectedAccount.username }}</h4>
|
<h4>{{ selectedAccount.username }}</h4>
|
||||||
<p>Selected</p>
|
<p>Selected</p>
|
||||||
@@ -37,7 +46,7 @@
|
|||||||
<div v-if="displayAccounts.length > 0" class="account-group">
|
<div v-if="displayAccounts.length > 0" class="account-group">
|
||||||
<div v-for="account in displayAccounts" :key="account.id" class="account-row">
|
<div v-for="account in displayAccounts" :key="account.id" class="account-row">
|
||||||
<Button class="option account" @click="setAccount(account)">
|
<Button class="option account" @click="setAccount(account)">
|
||||||
<Avatar :src="account.profile_picture" class="icon" />
|
<Avatar :src="`https://mc-heads.net/avatar/${account.id}/128`" class="icon" />
|
||||||
<p>{{ account.username }}</p>
|
<p>{{ account.username }}</p>
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||||
@@ -55,7 +64,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Avatar, Button, Card, PlusIcon, TrashIcon, UsersIcon, LogInIcon } from 'omorphia'
|
import { Avatar, Button, Card, PlusIcon, TrashIcon, UsersIcon, LogInIcon } from 'omorphia'
|
||||||
import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import {
|
import {
|
||||||
users,
|
users,
|
||||||
remove_user,
|
remove_user,
|
||||||
@@ -68,51 +77,41 @@ import { handleError } from '@/store/state.js'
|
|||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
expanded: {
|
mode: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
default: 'normal',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const settings = ref(await get().catch(handleError))
|
const emit = defineEmits(['change'])
|
||||||
|
|
||||||
const appendProfiles = (accounts) => {
|
const settings = ref({})
|
||||||
return accounts.map((account) => {
|
const accounts = ref([])
|
||||||
return {
|
async function refreshValues() {
|
||||||
...account,
|
settings.value = await get().catch(handleError)
|
||||||
profile_picture: `https://mc-heads.net/avatar/${account.id}/128`,
|
accounts.value = await users().catch(handleError)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
defineExpose({
|
||||||
const accounts = ref(await users().then(appendProfiles).catch(handleError))
|
refreshValues,
|
||||||
|
})
|
||||||
|
await refreshValues()
|
||||||
|
|
||||||
const displayAccounts = computed(() =>
|
const displayAccounts = computed(() =>
|
||||||
accounts.value.filter((account) => settings.value.default_user !== account.id)
|
accounts.value.filter((account) => settings.value.default_user !== account.id)
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedAccount = ref(
|
const selectedAccount = computed(() =>
|
||||||
accounts.value.find((account) => account.id === settings.value.default_user)
|
accounts.value.find((account) => account.id === settings.value.default_user)
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshValues = async () => {
|
async function setAccount(account) {
|
||||||
accounts.value = await users().then(appendProfiles).catch(handleError)
|
|
||||||
selectedAccount.value = accounts.value.find(
|
|
||||||
(account) => account.id === settings.value.default_user
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let showCard = ref(false)
|
|
||||||
let card = ref(null)
|
|
||||||
let button = ref(null)
|
|
||||||
|
|
||||||
const setAccount = async (account) => {
|
|
||||||
settings.value.default_user = account.id
|
settings.value.default_user = account.id
|
||||||
selectedAccount.value = account
|
|
||||||
await set(settings.value).catch(handleError)
|
await set(settings.value).catch(handleError)
|
||||||
|
emit('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
const login = async () => {
|
async function login() {
|
||||||
const url = await authenticate_begin_flow().catch(handleError)
|
const url = await authenticate_begin_flow().catch(handleError)
|
||||||
|
|
||||||
const window = new WebviewWindow('loginWindow', {
|
const window = new WebviewWindow('loginWindow', {
|
||||||
@@ -120,14 +119,6 @@ const login = async () => {
|
|||||||
url: url,
|
url: url,
|
||||||
})
|
})
|
||||||
|
|
||||||
window.once('tauri://created', function () {
|
|
||||||
console.log('webview created')
|
|
||||||
})
|
|
||||||
|
|
||||||
window.once('tauri://error', function (e) {
|
|
||||||
console.log('webview error', e)
|
|
||||||
})
|
|
||||||
|
|
||||||
const loggedIn = await authenticate_await_completion().catch(handleError)
|
const loggedIn = await authenticate_await_completion().catch(handleError)
|
||||||
await setAccount(loggedIn)
|
await setAccount(loggedIn)
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
@@ -141,14 +132,15 @@ const logout = async (id) => {
|
|||||||
if (!selectedAccount.value && accounts.value.length > 0) {
|
if (!selectedAccount.value && accounts.value.length > 0) {
|
||||||
await setAccount(accounts.value[0])
|
await setAccount(accounts.value[0])
|
||||||
await refreshValues()
|
await refreshValues()
|
||||||
|
} else {
|
||||||
|
emit('change')
|
||||||
}
|
}
|
||||||
mixpanel.track('AccountLogOut')
|
mixpanel.track('AccountLogOut')
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggle = () => {
|
let showCard = ref(false)
|
||||||
showCard.value = !showCard.value
|
let card = ref(null)
|
||||||
}
|
let button = ref(null)
|
||||||
|
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||||
if (
|
if (
|
||||||
@@ -219,6 +211,12 @@ onBeforeUnmount(() => {
|
|||||||
&.expanded {
|
&.expanded {
|
||||||
left: 13.5rem;
|
left: 13.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.isolated {
|
||||||
|
position: relative;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.accounts-title {
|
.accounts-title {
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ const hideContextMenu = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const optionClicked = (option) => {
|
const optionClicked = (option) => {
|
||||||
console.log('item check', item.value)
|
|
||||||
emit('option-clicked', {
|
emit('option-clicked', {
|
||||||
item: item.value,
|
item: item.value,
|
||||||
option: option,
|
option: option,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
|
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
|
||||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||||
import { defineExpose, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { handleError, useTheming } from '@/store/state.js'
|
import { handleError, useTheming } from '@/store/state.js'
|
||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
|
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
|
||||||
<div class="toggle-setting">
|
<div class="toggle-setting" :class="{ compact }">
|
||||||
<input
|
<input
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:disabled="props.disabled"
|
:disabled="props.disabled"
|
||||||
@@ -18,10 +18,7 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<span class="installation-buttons">
|
<span class="installation-buttons">
|
||||||
<Button
|
<Button :disabled="props.disabled" @click="autoDetect">
|
||||||
:disabled="props.disabled"
|
|
||||||
@click="$refs.detectJavaModal.show(props.version, props.modelValue)"
|
|
||||||
>
|
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
Auto detect
|
Auto detect
|
||||||
</Button>
|
</Button>
|
||||||
@@ -48,11 +45,12 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
|
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
|
||||||
import { get_jre } from '@/helpers/jre.js'
|
import { find_jre_17_jres, get_jre } from '@/helpers/jre.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { open } from '@tauri-apps/api/dialog'
|
import { open } from '@tauri-apps/api/dialog'
|
||||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
|
import { handleError } from '@/store/state.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
version: {
|
version: {
|
||||||
@@ -74,6 +72,10 @@ const props = defineProps({
|
|||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
compact: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
@@ -117,6 +119,18 @@ async function handleJavaFileInput() {
|
|||||||
emit('update:modelValue', result)
|
emit('update:modelValue', result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const detectJavaModal = ref(null)
|
||||||
|
async function autoDetect() {
|
||||||
|
if (!props.compact) {
|
||||||
|
detectJavaModal.value.show(props.version, props.modelValue)
|
||||||
|
} else {
|
||||||
|
let versions = await find_jre_17_jres().catch(handleError)
|
||||||
|
if (versions.length > 0) {
|
||||||
|
emit('update:modelValue', versions[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -131,12 +145,15 @@ async function handleJavaFileInput() {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.installation-buttons {
|
.installation-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -47,18 +47,7 @@
|
|||||||
<Card v-if="showCard === true" ref="card" class="info-card">
|
<Card v-if="showCard === true" ref="card" class="info-card">
|
||||||
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
|
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
|
||||||
<h3 class="info-title">
|
<h3 class="info-title">
|
||||||
{{ loadingBar.bar_type.pack_name ?? 'Installing Modpack' }}
|
{{ loadingBar.title }}
|
||||||
</h3>
|
|
||||||
<ProgressBar :progress="Math.floor(loadingBar.current)" />
|
|
||||||
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</transition>
|
|
||||||
<transition name="download">
|
|
||||||
<Card v-if="showCard === true" ref="card" class="info-card">
|
|
||||||
<div v-for="loadingBar in currentLoadingBars" :key="loadingBar.id" class="info-text">
|
|
||||||
<h3 class="info-title">
|
|
||||||
{{ loadingBar.bar_type.pack_name ?? 'Installing Modpack' }}
|
|
||||||
</h3>
|
</h3>
|
||||||
<ProgressBar :progress="Math.floor(loadingBar.current)" />
|
<ProgressBar :progress="Math.floor(loadingBar.current)" />
|
||||||
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
|
<div class="row">{{ Math.floor(loadingBar.current) }}% {{ loadingBar.message }}</div>
|
||||||
@@ -123,6 +112,7 @@ const profiles = ref(null)
|
|||||||
const infoButton = ref(null)
|
const infoButton = ref(null)
|
||||||
const profileButton = ref(null)
|
const profileButton = ref(null)
|
||||||
const showCard = ref(false)
|
const showCard = ref(false)
|
||||||
|
|
||||||
const showProfiles = ref(false)
|
const showProfiles = ref(false)
|
||||||
|
|
||||||
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
||||||
@@ -159,15 +149,25 @@ const goToTerminal = (path) => {
|
|||||||
router.push(`/instance/${encodeURIComponent(path ?? selectedProfile.value.path)}/logs`)
|
router.push(`/instance/${encodeURIComponent(path ?? selectedProfile.value.path)}/logs`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
|
const currentLoadingBars = ref([])
|
||||||
|
|
||||||
const unlistenLoading = await loading_listener(async () => {
|
|
||||||
await refreshInfo()
|
|
||||||
})
|
|
||||||
|
|
||||||
const refreshInfo = async () => {
|
const refreshInfo = async () => {
|
||||||
const currentLoadingBarCount = currentLoadingBars.value.length
|
const currentLoadingBarCount = currentLoadingBars.value.length
|
||||||
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError))
|
currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError)).map(
|
||||||
|
(x) => {
|
||||||
|
if (x.bar_type.type === 'java_download') {
|
||||||
|
x.title = 'Downloading Java ' + x.bar_type.version
|
||||||
|
}
|
||||||
|
if (x.bar_type.profile_name) {
|
||||||
|
x.title = x.bar_type.profile_name
|
||||||
|
}
|
||||||
|
if (x.bar_type.pack_name) {
|
||||||
|
x.title = x.bar_type.pack_name
|
||||||
|
}
|
||||||
|
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
)
|
||||||
if (currentLoadingBars.value.length === 0) {
|
if (currentLoadingBars.value.length === 0) {
|
||||||
showCard.value = false
|
showCard.value = false
|
||||||
} else if (currentLoadingBarCount < currentLoadingBars.value.length) {
|
} else if (currentLoadingBarCount < currentLoadingBars.value.length) {
|
||||||
@@ -175,6 +175,11 @@ const refreshInfo = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await refreshInfo()
|
||||||
|
const unlistenLoading = await loading_listener(async () => {
|
||||||
|
await refreshInfo()
|
||||||
|
})
|
||||||
|
|
||||||
const selectProfile = (profile) => {
|
const selectProfile = (profile) => {
|
||||||
selectedProfile.value = profile
|
selectedProfile.value = profile
|
||||||
showProfiles.value = false
|
showProfiles.value = false
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
import { handleError } from '@/store/state.js'
|
import { handleError } from '@/store/state.js'
|
||||||
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
|
|
||||||
export const useFetch = async (url, item) => {
|
export const useFetch = async (url, item) => {
|
||||||
try {
|
try {
|
||||||
|
const version = await getVersion()
|
||||||
|
|
||||||
return await ofetch(url, {
|
return await ofetch(url, {
|
||||||
headers: { 'User-Agent': 'modrinth/theseus (support@modrinth.com)' },
|
headers: { 'User-Agent': `modrinth/theseus/${version} (support@modrinth.com)` },
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError({ message: `Error fetching ${item}` })
|
handleError({ message: `Error fetching ${item}` })
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ export async function get_jre(path) {
|
|||||||
|
|
||||||
// Autodetect Java globals, by searching the users computer.
|
// Autodetect Java globals, by searching the users computer.
|
||||||
// Returns a *NEW* JavaGlobals that can be put into Settings
|
// Returns a *NEW* JavaGlobals that can be put into Settings
|
||||||
export async function autodetect_java_globals(path) {
|
export async function autodetect_java_globals() {
|
||||||
return await invoke('jre_autodetect_java_globals', { path })
|
return await invoke('jre_autodetect_java_globals')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Automatically installs specified java version
|
// Automatically installs specified java version
|
||||||
export async function jre_auto_install_java(javaVersion) {
|
export async function auto_install_java(javaVersion) {
|
||||||
return await invoke('jre_auto_install_java', { javaVersion })
|
return await invoke('jre_auto_install_java', { javaVersion })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
|||||||
const recentInstances = shallowRef([])
|
const recentInstances = shallowRef([])
|
||||||
|
|
||||||
const getInstances = async () => {
|
const getInstances = async () => {
|
||||||
console.log('aa')
|
|
||||||
const profiles = await list(true).catch(handleError)
|
const profiles = await list(true).catch(handleError)
|
||||||
console.log(profiles)
|
|
||||||
recentInstances.value = Object.values(profiles).sort((a, b) => {
|
recentInstances.value = Object.values(profiles).sort((a, b) => {
|
||||||
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
|
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -221,7 +221,6 @@ const handleRightClick = (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOptionsClick = async (args) => {
|
const handleOptionsClick = async (args) => {
|
||||||
console.log(args)
|
|
||||||
switch (args.option) {
|
switch (args.option) {
|
||||||
case 'play':
|
case 'play':
|
||||||
await startInstance('InstancePageContextMenu')
|
await startInstance('InstancePageContextMenu')
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ import {
|
|||||||
renderString,
|
renderString,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { releaseColor } from '@/helpers/utils'
|
import { releaseColor } from '@/helpers/utils'
|
||||||
import { ref, defineProps, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_playground"
|
name = "theseus_playground"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|||||||
Reference in New Issue
Block a user