You've already forked AstralRinth
forked from didirus/AstralRinth
Initial bug fixes (#127)
* Initial bug fixes * fix compile error on non-mac * Fix even more bugs * Fix more * fix more * fix build * fix build * address review comments
This commit is contained in:
33
.github/workflows/gui-build.yml
vendored
33
.github/workflows/gui-build.yml
vendored
@@ -13,19 +13,30 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: "theseus_gui"
|
||||
|
||||
node-version: 18.x
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable --immutable-cache --check-cache
|
||||
|
||||
run: pnpm install
|
||||
- name: Run Lint
|
||||
run: yarn run lint
|
||||
|
||||
run: pnpm lint
|
||||
- name: Build
|
||||
run: yarn run build
|
||||
run: pnpm build
|
||||
27
.github/workflows/tauri-build.yml
vendored
27
.github/workflows/tauri-build.yml
vendored
@@ -36,12 +36,27 @@ jobs:
|
||||
with:
|
||||
workspaces: './src-tauri -> target'
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: "theseus_gui"
|
||||
node-version: 18.x
|
||||
- name: Install pnpm via corepack
|
||||
shell: bash
|
||||
run: |
|
||||
corepack enable
|
||||
corepack prepare --activate
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: install dependencies (ubuntu only)
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
@@ -49,8 +64,8 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: install frontend dependencies
|
||||
run: yarn install --immutable --immutable-cache --check-cache
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
|
||||
838
Cargo.lock
generated
838
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ use crate::{launcher::auth as inner, State};
|
||||
use futures::prelude::*;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::state::AuthTask;
|
||||
pub use inner::Credentials;
|
||||
|
||||
/// Authenticate a user with Hydra - part 1
|
||||
@@ -11,8 +12,7 @@ pub use inner::Credentials;
|
||||
/// to call authenticate and call the flow from the frontend.
|
||||
/// Visit the URL in a browser, then call and await 'authenticate_await_complete_flow'.
|
||||
pub async fn authenticate_begin_flow() -> crate::Result<url::Url> {
|
||||
let st = State::get().await?.clone();
|
||||
let url = st.auth_flow.write().await.begin_auth().await?;
|
||||
let url = AuthTask::begin_auth().await?;
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
@@ -21,12 +21,15 @@ pub async fn authenticate_begin_flow() -> crate::Result<url::Url> {
|
||||
/// This can be used in conjunction with 'authenticate_begin_flow'
|
||||
/// to call authenticate and call the flow from the frontend.
|
||||
pub async fn authenticate_await_complete_flow() -> crate::Result<Credentials> {
|
||||
let st = State::get().await?.clone();
|
||||
let credentials =
|
||||
st.auth_flow.write().await.await_auth_completion().await?;
|
||||
let credentials = AuthTask::await_auth_completion().await?;
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
/// Cancels the active authentication flow
|
||||
pub async fn cancel_flow() -> crate::Result<()> {
|
||||
AuthTask::cancel().await
|
||||
}
|
||||
|
||||
/// Authenticate a user with Hydra
|
||||
/// To run this, you need to first spawn this function as a task, then
|
||||
/// open a browser to the given URL and finally wait on the spawned future
|
||||
@@ -38,7 +41,6 @@ pub async fn authenticate(
|
||||
) -> crate::Result<Credentials> {
|
||||
let mut flow = inner::HydraAuthFlow::new().await?;
|
||||
let state = State::get().await?;
|
||||
let mut users = state.users.write().await;
|
||||
|
||||
let url = flow.prepare_login_url().await?;
|
||||
browser_url.send(url).map_err(|url| {
|
||||
@@ -48,7 +50,10 @@ pub async fn authenticate(
|
||||
})?;
|
||||
|
||||
let credentials = flow.extract_credentials(&state.fetch_semaphore).await?;
|
||||
users.insert(&credentials).await?;
|
||||
{
|
||||
let mut users = state.users.write().await;
|
||||
users.insert(&credentials).await?;
|
||||
}
|
||||
|
||||
if state.settings.read().await.default_user.is_none() {
|
||||
let mut settings = state.settings.write().await;
|
||||
@@ -69,7 +74,7 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||
let fetch_semaphore = &state.fetch_semaphore;
|
||||
futures::future::ready(users.get(user).ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"Tried to refresh nonexistent user with ID {user}"
|
||||
"You are not logged in with a Minecraft account!"
|
||||
))
|
||||
.as_error()
|
||||
}))
|
||||
|
||||
@@ -79,6 +79,7 @@ enum PackDependency {
|
||||
#[tracing::instrument]
|
||||
#[theseus_macros::debug_pin]
|
||||
pub async fn install_pack_from_version_id(
|
||||
project_id: String,
|
||||
version_id: String,
|
||||
title: String,
|
||||
icon_url: Option<String>,
|
||||
@@ -91,7 +92,10 @@ pub async fn install_pack_from_version_id(
|
||||
None,
|
||||
None,
|
||||
icon_url.clone(),
|
||||
None,
|
||||
Some(LinkedData {
|
||||
project_id: Some(project_id),
|
||||
version_id: Some(version_id.clone()),
|
||||
}),
|
||||
Some(true),
|
||||
)
|
||||
.await?;
|
||||
@@ -240,261 +244,277 @@ async fn install_pack(
|
||||
) -> crate::Result<PathBuf> {
|
||||
let state = &State::get().await?;
|
||||
|
||||
let reader: Cursor<&bytes::Bytes> = Cursor::new(&file);
|
||||
let result = async {
|
||||
let reader: Cursor<&bytes::Bytes> = Cursor::new(&file);
|
||||
|
||||
// Create zip reader around file
|
||||
let mut zip_reader = ZipFileReader::new(reader).await.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to read input modpack zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
// Create zip reader around file
|
||||
let mut zip_reader =
|
||||
ZipFileReader::new(reader).await.map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to read input modpack zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
|
||||
// Extract index of modrinth.index.json
|
||||
let zip_index_option = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.iter()
|
||||
.position(|f| f.entry().filename() == "modrinth.index.json");
|
||||
if let Some(zip_index) = zip_index_option {
|
||||
let mut manifest = String::new();
|
||||
let entry = zip_reader
|
||||
// Extract index of modrinth.index.json
|
||||
let zip_index_option = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.get(zip_index)
|
||||
.unwrap()
|
||||
.entry()
|
||||
.clone();
|
||||
let mut reader = zip_reader.entry(zip_index).await?;
|
||||
reader.read_to_string_checked(&mut manifest, &entry).await?;
|
||||
.iter()
|
||||
.position(|f| f.entry().filename() == "modrinth.index.json");
|
||||
if let Some(zip_index) = zip_index_option {
|
||||
let mut manifest = String::new();
|
||||
let entry = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.get(zip_index)
|
||||
.unwrap()
|
||||
.entry()
|
||||
.clone();
|
||||
let mut reader = zip_reader.entry(zip_index).await?;
|
||||
reader.read_to_string_checked(&mut manifest, &entry).await?;
|
||||
|
||||
let pack: PackFormat = serde_json::from_str(&manifest)?;
|
||||
let pack: PackFormat = serde_json::from_str(&manifest)?;
|
||||
|
||||
if &*pack.game != "minecraft" {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack does not support Minecraft".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let mut game_version = None;
|
||||
let mut mod_loader = None;
|
||||
let mut loader_version = None;
|
||||
for (key, value) in &pack.dependencies {
|
||||
match key {
|
||||
PackDependency::Forge => {
|
||||
mod_loader = Some(ModLoader::Forge);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::FabricLoader => {
|
||||
mod_loader = Some(ModLoader::Fabric);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::QuiltLoader => {
|
||||
mod_loader = Some(ModLoader::Quilt);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::Minecraft => game_version = Some(value),
|
||||
if &*pack.game != "minecraft" {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack does not support Minecraft".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
let game_version = if let Some(game_version) = game_version {
|
||||
game_version
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack did not specify Minecraft version".to_string(),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
let loader_version =
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||
loader_version.cloned(),
|
||||
)
|
||||
.await?;
|
||||
crate::api::profile::edit(&profile, |prof| {
|
||||
prof.metadata.name =
|
||||
override_title.clone().unwrap_or_else(|| pack.name.clone());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.linked_data = Some(LinkedData {
|
||||
project_id: project_id.clone(),
|
||||
version_id: version_id.clone(),
|
||||
});
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla);
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
State::sync().await?;
|
||||
|
||||
let profile = profile.clone();
|
||||
let result = async {
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::PackDownload {
|
||||
profile_path: profile.clone(),
|
||||
pack_name: pack.name.clone(),
|
||||
icon,
|
||||
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::<PackFile, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
70.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::<Vec<&str>>(),
|
||||
project
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| &**x),
|
||||
&state.fetch_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(())
|
||||
let mut game_version = None;
|
||||
let mut mod_loader = None;
|
||||
let mut loader_version = None;
|
||||
for (key, value) in &pack.dependencies {
|
||||
match key {
|
||||
PackDependency::Forge => {
|
||||
mod_loader = Some(ModLoader::Forge);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
PackDependency::FabricLoader => {
|
||||
mod_loader = Some(ModLoader::Fabric);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::QuiltLoader => {
|
||||
mod_loader = Some(ModLoader::Quilt);
|
||||
loader_version = Some(value);
|
||||
}
|
||||
PackDependency::Minecraft => game_version = Some(value),
|
||||
}
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
|
||||
let game_version = if let Some(game_version) = game_version {
|
||||
game_version
|
||||
} else {
|
||||
return Err(crate::ErrorKind::InputError(
|
||||
"Pack did not specify Minecraft version".to_string(),
|
||||
)
|
||||
.into());
|
||||
};
|
||||
|
||||
let loader_version =
|
||||
crate::profile_create::get_loader_version_from_loader(
|
||||
game_version.clone(),
|
||||
mod_loader.unwrap_or(ModLoader::Vanilla),
|
||||
loader_version.cloned(),
|
||||
)
|
||||
.await?;
|
||||
crate::api::profile::edit(&profile, |prof| {
|
||||
prof.metadata.name =
|
||||
override_title.clone().unwrap_or_else(|| pack.name.clone());
|
||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||
prof.metadata.linked_data = Some(LinkedData {
|
||||
project_id: project_id.clone(),
|
||||
version_id: version_id.clone(),
|
||||
});
|
||||
prof.metadata.icon = icon.clone();
|
||||
prof.metadata.game_version = game_version.clone();
|
||||
prof.metadata.loader_version = loader_version.clone();
|
||||
prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla);
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
State::sync().await?;
|
||||
|
||||
let profile = profile.clone();
|
||||
let result = async {
|
||||
let loading_bar = init_or_edit_loading(
|
||||
existing_loading_bar,
|
||||
LoadingBarType::PackDownload {
|
||||
profile_path: profile.clone(),
|
||||
pack_name: pack.name.clone(),
|
||||
icon,
|
||||
pack_id: project_id,
|
||||
pack_version: version_id,
|
||||
},
|
||||
100.0,
|
||||
"Downloading modpack",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut total_len = 0;
|
||||
let num_files = pack.files.len();
|
||||
use futures::StreamExt;
|
||||
loading_try_for_each_concurrent(
|
||||
futures::stream::iter(pack.files.into_iter())
|
||||
.map(Ok::<PackFile, crate::Error>),
|
||||
None,
|
||||
Some(&loading_bar),
|
||||
70.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(());
|
||||
}
|
||||
}
|
||||
|
||||
for index in 0..zip_reader.file().entries().len() {
|
||||
let file =
|
||||
zip_reader.file().entries().get(index).unwrap().entry();
|
||||
let file = fetch_mirrors(
|
||||
&project
|
||||
.downloads
|
||||
.iter()
|
||||
.map(|x| &**x)
|
||||
.collect::<Vec<&str>>(),
|
||||
project
|
||||
.hashes
|
||||
.get(&PackFileHash::Sha1)
|
||||
.map(|x| &**x),
|
||||
&state.fetch_semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if (file.filename().starts_with("overrides")
|
||||
|| file.filename().starts_with("client_overrides"))
|
||||
&& !file.filename().ends_with('/')
|
||||
{
|
||||
total_len += 1;
|
||||
}
|
||||
}
|
||||
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?;
|
||||
|
||||
for index in 0..zip_reader.file().entries().len() {
|
||||
let file = zip_reader
|
||||
.file()
|
||||
.entries()
|
||||
.get(index)
|
||||
.unwrap()
|
||||
.entry()
|
||||
.clone();
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))
|
||||
.await?;
|
||||
|
||||
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 total_len = 0;
|
||||
|
||||
let mut new_path = PathBuf::new();
|
||||
let components = file_path.components().skip(1);
|
||||
for index in 0..zip_reader.file().entries().len() {
|
||||
let file =
|
||||
zip_reader.file().entries().get(index).unwrap().entry();
|
||||
|
||||
for component in components {
|
||||
new_path.push(component);
|
||||
if (file.filename().starts_with("overrides")
|
||||
|| file.filename().starts_with("client_overrides"))
|
||||
&& !file.filename().ends_with('/')
|
||||
{
|
||||
total_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if new_path.file_name().is_some() {
|
||||
write(
|
||||
&profile.join(new_path),
|
||||
&content,
|
||||
&state.io_semaphore,
|
||||
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?;
|
||||
}
|
||||
}
|
||||
|
||||
emit_loading(
|
||||
&loading_bar,
|
||||
30.0 / total_len as f64,
|
||||
Some(&format!(
|
||||
"Extracting override {}/{}",
|
||||
index, total_len
|
||||
)),
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(
|
||||
&profile_val,
|
||||
Some(loading_bar),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(profile_val) =
|
||||
crate::api::profile::get(&profile, None).await?
|
||||
{
|
||||
crate::launcher::install_minecraft(
|
||||
&profile_val,
|
||||
Some(loading_bar),
|
||||
)
|
||||
.await?;
|
||||
Ok::<PathBuf, crate::Error>(profile.clone())
|
||||
}
|
||||
.await;
|
||||
|
||||
Ok::<PathBuf, crate::Error>(profile.clone())
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(crate::Error::from(crate::ErrorKind::InputError(
|
||||
"No pack manifest found in mrpack".to_string(),
|
||||
)))
|
||||
}
|
||||
.await;
|
||||
}
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
Err(err)
|
||||
}
|
||||
} else {
|
||||
let _ = crate::api::profile::remove(&profile).await;
|
||||
|
||||
Err(crate::Error::from(crate::ErrorKind::InputError(
|
||||
"No pack manifest found in mrpack".to_string(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ where
|
||||
|
||||
match profiles.0.get_mut(path) {
|
||||
Some(ref mut profile) => {
|
||||
action(profile).await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path.clone(),
|
||||
@@ -79,7 +81,7 @@ where
|
||||
)
|
||||
.await?;
|
||||
|
||||
action(profile).await
|
||||
Ok(())
|
||||
}
|
||||
None => Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
path.display().to_string(),
|
||||
@@ -102,6 +104,15 @@ pub async fn edit_icon(
|
||||
|
||||
match profiles.0.get_mut(path) {
|
||||
Some(ref mut profile) => {
|
||||
profile
|
||||
.set_icon(
|
||||
&state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
bytes::Bytes::from(bytes),
|
||||
&icon.to_string_lossy(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path.clone(),
|
||||
@@ -110,14 +121,7 @@ pub async fn edit_icon(
|
||||
)
|
||||
.await?;
|
||||
|
||||
profile
|
||||
.set_icon(
|
||||
&state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
bytes::Bytes::from(bytes),
|
||||
&icon.to_string_lossy(),
|
||||
)
|
||||
.await
|
||||
Ok(())
|
||||
}
|
||||
None => Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
path.display().to_string(),
|
||||
@@ -265,7 +269,8 @@ pub async fn update_all(
|
||||
|
||||
async move {
|
||||
let new_path =
|
||||
update_project(profile_path, &project).await?;
|
||||
update_project(profile_path, &project, Some(true))
|
||||
.await?;
|
||||
|
||||
map.write().await.insert(project, new_path);
|
||||
|
||||
@@ -276,6 +281,14 @@ pub async fn update_all(
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Arc::try_unwrap(map).unwrap().into_inner())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -290,6 +303,7 @@ pub async fn update_all(
|
||||
pub async fn update_project(
|
||||
profile_path: &Path,
|
||||
project_path: &Path,
|
||||
skip_send_event: Option<bool>,
|
||||
) -> crate::Result<PathBuf> {
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
if let Some(project) = profile.projects.get(project_path) {
|
||||
@@ -322,6 +336,16 @@ pub async fn update_project(
|
||||
}
|
||||
}
|
||||
|
||||
if !skip_send_event.unwrap_or(false) {
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
@@ -347,6 +371,14 @@ pub async fn add_project_from_version(
|
||||
if let Some(profile) = get(profile_path, None).await? {
|
||||
let (path, _) = profile.add_project_version(version_id).await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -379,6 +411,14 @@ pub async fn add_project_from_path(
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
@@ -395,7 +435,17 @@ pub async fn toggle_disable_project(
|
||||
project: &Path,
|
||||
) -> crate::Result<PathBuf> {
|
||||
if let Some(profile) = get(profile, None).await? {
|
||||
Ok(profile.toggle_disable_project(project).await?)
|
||||
let res = profile.toggle_disable_project(project).await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
profile.display().to_string(),
|
||||
@@ -413,6 +463,14 @@ pub async fn remove_project(
|
||||
if let Some(profile) = get(profile, None).await? {
|
||||
profile.remove_project(project, None).await?;
|
||||
|
||||
emit_profile(
|
||||
profile.uuid,
|
||||
profile.path,
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Edited,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(crate::ErrorKind::UnmanagedProfileError(
|
||||
|
||||
@@ -96,44 +96,57 @@ pub async fn profile_create(
|
||||
let path = canonicalize(&path)?;
|
||||
let mut profile =
|
||||
Profile::new(uuid, name, game_version, path.clone()).await?;
|
||||
if let Some(ref icon) = icon {
|
||||
let bytes = tokio::fs::read(icon).await?;
|
||||
profile
|
||||
.set_icon(
|
||||
&state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
bytes::Bytes::from(bytes),
|
||||
&icon.to_string_lossy(),
|
||||
)
|
||||
.await?;
|
||||
let result = async {
|
||||
if let Some(ref icon) = icon {
|
||||
let bytes = tokio::fs::read(icon).await?;
|
||||
profile
|
||||
.set_icon(
|
||||
&state.directories.caches_dir(),
|
||||
&state.io_semaphore,
|
||||
bytes::Bytes::from(bytes),
|
||||
&icon.to_string_lossy(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
profile.metadata.icon_url = icon_url;
|
||||
if let Some(loader_version) = loader {
|
||||
profile.metadata.loader = modloader;
|
||||
profile.metadata.loader_version = Some(loader_version);
|
||||
}
|
||||
|
||||
profile.metadata.linked_data = linked_data;
|
||||
|
||||
emit_profile(
|
||||
uuid,
|
||||
path.clone(),
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Created,
|
||||
)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile.clone()).await?;
|
||||
}
|
||||
|
||||
if !skip_install_profile.unwrap_or(false) {
|
||||
crate::launcher::install_minecraft(&profile, None).await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
profile.metadata.icon_url = icon_url;
|
||||
if let Some(loader_version) = loader {
|
||||
profile.metadata.loader = modloader;
|
||||
profile.metadata.loader_version = Some(loader_version);
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(profile) => Ok(profile),
|
||||
Err(err) => {
|
||||
let _ = crate::api::profile::remove(&profile.path).await;
|
||||
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
profile.metadata.linked_data = linked_data;
|
||||
|
||||
emit_profile(
|
||||
uuid,
|
||||
path.clone(),
|
||||
&profile.metadata.name,
|
||||
ProfilePayloadType::Created,
|
||||
)
|
||||
.await?;
|
||||
|
||||
{
|
||||
let mut profiles = state.profiles.write().await;
|
||||
profiles.insert(profile.clone()).await?;
|
||||
}
|
||||
|
||||
if !skip_install_profile.unwrap_or(false) {
|
||||
crate::launcher::install_minecraft(&profile, None).await?;
|
||||
}
|
||||
State::sync().await?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
|
||||
@@ -137,10 +137,6 @@ pub fn get_jvm_arguments(
|
||||
parsed_arguments.push("-cp".to_string());
|
||||
parsed_arguments.push(class_paths.to_string());
|
||||
}
|
||||
|
||||
if let Some(minimum) = memory.minimum {
|
||||
parsed_arguments.push(format!("-Xms{minimum}M"));
|
||||
}
|
||||
parsed_arguments.push(format!("-Xmx{}M", memory.maximum));
|
||||
for arg in custom_args {
|
||||
if !arg.is_empty() {
|
||||
|
||||
@@ -34,7 +34,11 @@ pub fn parse_rule(rule: &d::minecraft::Rule, java_version: &str) -> bool {
|
||||
Rule {
|
||||
features: Some(ref features),
|
||||
..
|
||||
} => features.has_demo_resolution.unwrap_or(false),
|
||||
} => {
|
||||
features.has_demo_resolution.unwrap_or(false)
|
||||
|| (features.has_demo_resolution.is_none()
|
||||
&& features.is_demo_user.is_none())
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ impl AuthTask {
|
||||
AuthTask(None)
|
||||
}
|
||||
|
||||
pub async fn begin_auth(&mut self) -> crate::Result<url::Url> {
|
||||
pub async fn begin_auth() -> crate::Result<url::Url> {
|
||||
let state = crate::State::get().await?;
|
||||
|
||||
// Creates a channel to receive the URL
|
||||
let (tx, rx) = tokio::sync::oneshot::channel::<url::Url>();
|
||||
let task = tokio::spawn(crate::auth::authenticate(tx));
|
||||
@@ -29,16 +31,20 @@ impl AuthTask {
|
||||
};
|
||||
|
||||
// Flow is going, store in state and return
|
||||
self.0 = Some(task);
|
||||
let mut write = state.auth_flow.write().await;
|
||||
write.0 = Some(task);
|
||||
|
||||
Ok(url)
|
||||
}
|
||||
|
||||
pub async fn await_auth_completion(
|
||||
&mut self,
|
||||
) -> crate::Result<Credentials> {
|
||||
pub async fn await_auth_completion() -> crate::Result<Credentials> {
|
||||
// Gets the task handle from the state, replacing with None
|
||||
let task = mem::replace(&mut self.0, None);
|
||||
let task = {
|
||||
let state = crate::State::get().await?;
|
||||
let mut write = state.auth_flow.write().await;
|
||||
|
||||
mem::replace(&mut write.0, None)
|
||||
};
|
||||
|
||||
// Waits for the task to complete, and returns the credentials
|
||||
let credentials = task
|
||||
@@ -49,13 +55,20 @@ impl AuthTask {
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
pub async fn cancel(&mut self) {
|
||||
pub async fn cancel() -> crate::Result<()> {
|
||||
// Gets the task handle from the state, replacing with None
|
||||
let task = mem::replace(&mut self.0, None);
|
||||
let task = {
|
||||
let state = crate::State::get().await?;
|
||||
let mut write = state.auth_flow.write().await;
|
||||
|
||||
mem::replace(&mut write.0, None)
|
||||
};
|
||||
if let Some(task) = task {
|
||||
// Cancels the task
|
||||
task.abort();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,14 +403,6 @@ impl Profile {
|
||||
}
|
||||
}
|
||||
|
||||
emit_profile(
|
||||
self.uuid,
|
||||
self.path.clone(),
|
||||
&self.metadata.name,
|
||||
ProfilePayloadType::Synced,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,7 @@ pub enum ProjectMetadata {
|
||||
authors: Vec<String>,
|
||||
version: Option<String>,
|
||||
icon: Option<PathBuf>,
|
||||
project_type: Option<String>,
|
||||
},
|
||||
Unknown,
|
||||
}
|
||||
@@ -484,6 +485,7 @@ pub async fn infer_data_from_files(
|
||||
.unwrap_or_default(),
|
||||
version: pack.version.clone(),
|
||||
icon,
|
||||
project_type: Some("mod".to_string()),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -544,6 +546,7 @@ pub async fn infer_data_from_files(
|
||||
authors: pack.author_list.unwrap_or_default(),
|
||||
version: pack.version,
|
||||
icon,
|
||||
project_type: Some("mod".to_string()),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -612,6 +615,7 @@ pub async fn infer_data_from_files(
|
||||
.collect(),
|
||||
version: Some(pack.version),
|
||||
icon,
|
||||
project_type: Some("mod".to_string()),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -687,6 +691,7 @@ pub async fn infer_data_from_files(
|
||||
.unwrap_or_default(),
|
||||
version: Some(pack.version),
|
||||
icon,
|
||||
project_type: Some("mod".to_string()),
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -735,6 +740,7 @@ pub async fn infer_data_from_files(
|
||||
authors: Vec::new(),
|
||||
version: None,
|
||||
icon,
|
||||
project_type: None,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -118,17 +118,12 @@ pub enum Theme {
|
||||
/// Minecraft memory settings
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
|
||||
pub struct MemorySettings {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub minimum: Option<u32>,
|
||||
pub maximum: u32,
|
||||
}
|
||||
|
||||
impl Default for MemorySettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
minimum: None,
|
||||
maximum: 2048,
|
||||
}
|
||||
Self { maximum: 2048 }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,17 @@ pub async fn get_all_jre() -> Result<Vec<JavaVersion>, JREError> {
|
||||
// Add JRES directly on PATH
|
||||
jre_paths.extend(get_all_jre_path().await?);
|
||||
jre_paths.extend(get_all_autoinstalled_jre_path().await?);
|
||||
if let Ok(java_home) = env::var("JAVA_HOME") {
|
||||
jre_paths.insert(PathBuf::from(java_home));
|
||||
}
|
||||
|
||||
// Hard paths for locations for commonly installed .exes
|
||||
let java_paths = [r"C:/Program Files/Java", r"C:/Program Files (x86)/Java"];
|
||||
let java_paths = [
|
||||
r"C:/Program Files/Java",
|
||||
r"C:/Program Files (x86)/Java",
|
||||
r"C:\Program Files\Eclipse Adoptium",
|
||||
r"C:\Program Files (x86)\Eclipse Adoptium",
|
||||
];
|
||||
for java_path in java_paths {
|
||||
let Ok(java_subpaths) = std::fs::read_dir(java_path) else {continue };
|
||||
for java_subpath in java_subpaths {
|
||||
@@ -201,7 +209,6 @@ async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
||||
let contents = std::fs::read_to_string(file_path)?;
|
||||
|
||||
let entry = entry.path().join(contents);
|
||||
println!("{:?}", entry);
|
||||
jre_paths.insert(entry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,30 +9,32 @@
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"lint:js": "eslint --ext .js,.vue,.ts,.jsx,.tsx,.html,.vue .",
|
||||
"lint": "npm run lint:js && prettier --check .",
|
||||
"lint": "pnpm run lint:js && prettier --check .",
|
||||
"fix": "eslint --fix --ext .js,.vue,.ts,.jsx,.tsx,.html,.vue . && prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0",
|
||||
"@tauri-apps/api": "^1.3.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"floating-vue": "^2.0.0-beta.20",
|
||||
"ofetch": "^1.0.1",
|
||||
"omorphia": "^0.4.17",
|
||||
"pinia": "^2.0.33",
|
||||
"omorphia": "^0.4.22",
|
||||
"pinia": "^2.1.3",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue": "^3.2.45",
|
||||
"vue-multiselect": "^3.0.0-alpha.2",
|
||||
"vue-router": "4"
|
||||
"vue": "^3.3.4",
|
||||
"vue-multiselect": "^3.0.0-beta.2",
|
||||
"vue-router": "4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-alias": "^4.0.3",
|
||||
"@tauri-apps/cli": "^1.2.2",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-vue": "^9.9.0",
|
||||
"prettier": "^2.8.7",
|
||||
"sass": "^1.58.3",
|
||||
"vite": "^4.0.0",
|
||||
"@rollup/plugin-alias": "^4.0.4",
|
||||
"@tauri-apps/cli": "^1.3.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"eslint": "^8.41.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"prettier": "^2.8.8",
|
||||
"sass": "^1.62.1",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-eslint": "^1.8.1"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.5.1"
|
||||
}
|
||||
|
||||
1782
theseus_gui/pnpm-lock.yaml
generated
Normal file
1782
theseus_gui/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ build = "build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.2", features = [] }
|
||||
tauri-build = { version = "1.3", features = [] }
|
||||
regex = "1.5"
|
||||
|
||||
[dependencies]
|
||||
@@ -19,7 +19,9 @@ theseus = { path = "../../theseus", features = ["tauri"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.2", features = ["devtools", "dialog", "dialog-open", "protocol-asset", "shell-open", "updater", "window-close", "window-create"] }
|
||||
tauri = { version = "1.3", features = ["devtools", "dialog", "dialog-open", "protocol-asset", "shell-open", "updater", "window-close", "window-create"] }
|
||||
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" }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
|
||||
@@ -16,6 +16,11 @@ pub async fn auth_authenticate_await_completion() -> Result<Credentials> {
|
||||
Ok(auth::authenticate_await_complete_flow().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn auth_cancel_flow() -> Result<()> {
|
||||
Ok(auth::cancel_flow().await?)
|
||||
}
|
||||
|
||||
/// Refresh some credentials using Hydra, if needed
|
||||
// invoke('auth_refresh',user)
|
||||
#[tauri::command]
|
||||
|
||||
@@ -18,11 +18,7 @@ pub async fn logs_get_logs(
|
||||
profile_uuid: Uuid,
|
||||
clear_contents: Option<bool>,
|
||||
) -> Result<Vec<Logs>> {
|
||||
use std::time::Instant;
|
||||
let now = Instant::now();
|
||||
let val = logs::get_logs(profile_uuid, clear_contents).await?;
|
||||
let elapsed = now.elapsed();
|
||||
println!("Elapsed: {:.2?}", elapsed);
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ pub mod profile;
|
||||
pub mod profile_create;
|
||||
pub mod settings;
|
||||
pub mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||
|
||||
|
||||
@@ -4,13 +4,15 @@ use theseus::prelude::*;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pack_install_version_id(
|
||||
project_id: String,
|
||||
version_id: String,
|
||||
pack_title: String,
|
||||
pack_icon: Option<String>,
|
||||
) -> Result<PathBuf> {
|
||||
let res =
|
||||
pack::install_pack_from_version_id(version_id, pack_title, pack_icon)
|
||||
.await?;
|
||||
let res = pack::install_pack_from_version_id(
|
||||
project_id, version_id, pack_title, pack_icon,
|
||||
)
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn profile_update_project(
|
||||
path: &Path,
|
||||
project_path: &Path,
|
||||
) -> Result<PathBuf> {
|
||||
Ok(profile::update_project(path, project_path).await?)
|
||||
Ok(profile::update_project(path, project_path, None).await?)
|
||||
}
|
||||
|
||||
// Adds a project to a profile from a version ID
|
||||
|
||||
76
theseus_gui/src-tauri/src/api/utils.rs
Normal file
76
theseus_gui/src-tauri/src/api/utils.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use crate::api::Result;
|
||||
use std::process::Command;
|
||||
|
||||
// cfg only on mac os
|
||||
// disables mouseover and fixes a random crash error only fixed by recent versions of macos
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tauri::command]
|
||||
pub async fn should_disable_mouseover() -> bool {
|
||||
// We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety
|
||||
let os = os_info::get();
|
||||
if let os_info::Version::Semantic(major, minor, _) = os.version() {
|
||||
if *major >= 12 && *minor >= 3 {
|
||||
// Mac os version is 12.3 or higher, we allow mouseover
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[tauri::command]
|
||||
pub async fn should_disable_mouseover() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn show_in_folder(path: String) -> Result<()> {
|
||||
{
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Command::new("explorer")
|
||||
.args(["/select,", &path]) // The comma after select is not a typo
|
||||
.spawn()?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
use std::fs;
|
||||
use std::fs::metadata;
|
||||
use std::path::PathBuf;
|
||||
|
||||
if path.contains(",") {
|
||||
// see https://gitlab.freedesktop.org/dbus/dbus/-/issues/76
|
||||
let new_path = match metadata(&path)?.is_dir() {
|
||||
true => path.clone(),
|
||||
false => {
|
||||
let mut path2 = PathBuf::from(path.clone());
|
||||
path2.pop();
|
||||
path2.to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
Command::new("xdg-open").arg(&new_path).spawn()?;
|
||||
} else {
|
||||
Command::new("dbus-send")
|
||||
.args([
|
||||
"--session",
|
||||
"--dest=org.freedesktop.FileManager1",
|
||||
"--type=method_call",
|
||||
"/org/freedesktop/FileManager1",
|
||||
"org.freedesktop.FileManager1.ShowItems",
|
||||
format!("array:string:\"file://{path}\"").as_str(),
|
||||
"string:\"\"",
|
||||
])
|
||||
.spawn()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Command::new("open").args(["-R", &path]).spawn()?;
|
||||
}
|
||||
|
||||
Ok::<(), theseus::Error>(())
|
||||
}?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
use theseus::prelude::*;
|
||||
|
||||
use tauri::Manager;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
@@ -20,29 +21,14 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// cfg only on mac os
|
||||
// disables mouseover and fixes a random crash error only fixed by recent versions of macos
|
||||
#[cfg(target_os = "macos")]
|
||||
#[tauri::command]
|
||||
async fn should_disable_mouseover() -> bool {
|
||||
// We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety
|
||||
let os = os_info::get();
|
||||
if let os_info::Version::Semantic(major, minor, _) = os.version() {
|
||||
if *major >= 12 && *minor >= 3 {
|
||||
// Mac os version is 12.3 or higher, we allow mouseover
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[tauri::command]
|
||||
async fn should_disable_mouseover() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct Payload {
|
||||
args: Vec<String>,
|
||||
cwd: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
/*
|
||||
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
|
||||
@@ -70,9 +56,13 @@ fn main() {
|
||||
.expect("setting default subscriber failed");
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
initialize_state,
|
||||
should_disable_mouseover,
|
||||
api::progress_bars_list,
|
||||
api::profile_create::profile_create_empty,
|
||||
api::profile_create::profile_create,
|
||||
@@ -98,6 +88,7 @@ fn main() {
|
||||
api::pack::pack_install_file,
|
||||
api::auth::auth_authenticate_begin_flow,
|
||||
api::auth::auth_authenticate_await_completion,
|
||||
api::auth::auth_cancel_flow,
|
||||
api::auth::auth_refresh,
|
||||
api::auth::auth_remove_user,
|
||||
api::auth::auth_has_user,
|
||||
@@ -141,6 +132,8 @@ fn main() {
|
||||
api::logs::logs_get_stderr_by_datetime,
|
||||
api::logs::logs_delete_logs,
|
||||
api::logs::logs_delete_logs_by_datetime,
|
||||
api::utils::show_in_folder,
|
||||
api::utils::should_disable_mouseover,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "yarn dev",
|
||||
"beforeBuildCommand": "yarn build",
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
|
||||
@@ -53,6 +53,33 @@ const notificationsWrapper = ref(null)
|
||||
watch(notificationsWrapper, () => {
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
})
|
||||
|
||||
// Link handler
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
if (
|
||||
target.href &&
|
||||
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
||||
!target.classList.contains('router-link-active') &&
|
||||
!target.href.startsWith('http://localhost') &&
|
||||
!target.href.startsWith('https://tauri.localhost')
|
||||
) {
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: target.href,
|
||||
},
|
||||
})
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@import 'inter.scss';
|
||||
|
||||
:root {
|
||||
font-family: var(--font-standard);
|
||||
color-scheme: dark;
|
||||
@@ -7,10 +5,15 @@
|
||||
--expanded-view-width: calc(100% - 13rem);
|
||||
}
|
||||
|
||||
body {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
@@ -41,130 +44,7 @@ a {
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
color: var(--color-base) !important;
|
||||
outline: 2px solid transparent;
|
||||
max-width: 15rem;
|
||||
width: 100% !important;
|
||||
|
||||
.multiselect__input:focus-visible {
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
padding: 0 !important;
|
||||
min-height: 0 !important;
|
||||
font-weight: normal !important;
|
||||
margin-left: 0.5rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: none !important;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
input::placeholder {
|
||||
color: var(--color-base);
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-button-bg);
|
||||
box-shadow: var(--shadow-inset-sm);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding-left: 0.5rem;
|
||||
font-size: 1rem;
|
||||
|
||||
transition: background-color 0.1s ease-in-out;
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
|
||||
.multiselect__spinner {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__single {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-base);
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-brand);
|
||||
}
|
||||
|
||||
.multiselect__tag-icon {
|
||||
background: transparent;
|
||||
|
||||
&:after {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
color: var(--color-base);
|
||||
margin-left: 0.5rem;
|
||||
opacity: 0.6;
|
||||
font-size: 1rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
background: var(--color-button-bg);
|
||||
border: none;
|
||||
overflow-x: hidden;
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
width: 100%;
|
||||
|
||||
.multiselect__element {
|
||||
.multiselect__option--highlight {
|
||||
background: var(--color-button-bg);
|
||||
filter: brightness(1.25);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background: var(--color-brand);
|
||||
font-weight: bold;
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__spinner {
|
||||
background: var(--color-button-bg);
|
||||
|
||||
&:active {
|
||||
filter: brightness(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
&.multiselect--disabled {
|
||||
background: none;
|
||||
|
||||
.multiselect__current,
|
||||
.multiselect__select {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-top: none !important;
|
||||
border-top-left-radius: var(--radius-md) !important;
|
||||
border-top-right-radius: var(--radius-md) !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.mod-text {
|
||||
@@ -173,7 +53,3 @@ input {
|
||||
gap: 1rem;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
input {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// TODO: move to omorphia
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Regular.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Medium.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-SemiBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-Bold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@font-face {
|
||||
font-family: inter;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff2?v=3.19') format('woff2'),
|
||||
url('https://cdn-raw.modrinth.com/fonts/inter/Inter-ExtraBold.woff?v=3.19') format('woff');
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -9,19 +8,11 @@ const props = defineProps({
|
||||
return []
|
||||
},
|
||||
},
|
||||
news: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
},
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
canPaginate: Boolean,
|
||||
})
|
||||
const modsRow = ref(null)
|
||||
</script>
|
||||
<template>
|
||||
<div class="row">
|
||||
@@ -29,7 +20,7 @@ const modsRow = ref(null)
|
||||
<p>{{ props.label }}</p>
|
||||
<hr />
|
||||
</div>
|
||||
<section ref="modsRow" class="instances">
|
||||
<section class="instances">
|
||||
<Instance
|
||||
v-for="instance in props.instances"
|
||||
:key="instance.id"
|
||||
@@ -56,6 +47,7 @@ const modsRow = ref(null)
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
@@ -101,7 +93,6 @@ const modsRow = ref(null)
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-right: auto;
|
||||
margin-top: 0.8rem;
|
||||
scroll-behavior: smooth;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -42,16 +42,10 @@ onMounted(() => {
|
||||
|
||||
handlePaginationDisplay()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (props.canPaginate) window.removeEventListener('resize', handlePaginationDisplay)
|
||||
})
|
||||
|
||||
const handleLeftPage = () => {
|
||||
modsRow.value.scrollLeft -= 170
|
||||
}
|
||||
const handleRightPage = () => {
|
||||
modsRow.value.scrollLeft += 170
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div v-if="props.instances.length > 0" class="row">
|
||||
@@ -59,8 +53,8 @@ const handleRightPage = () => {
|
||||
<p>{{ props.label }}</p>
|
||||
<hr aria-hidden="true" />
|
||||
<div v-if="allowPagination" class="pagination">
|
||||
<ChevronLeftIcon role="button" @click="handleLeftPage" />
|
||||
<ChevronRightIcon role="button" @click="handleRightPage" />
|
||||
<ChevronLeftIcon role="button" @click="modsRow.value.scrollLeft -= 170" />
|
||||
<ChevronRightIcon role="button" @click="modsRow.value.scrollLeft += 170" />
|
||||
</div>
|
||||
</div>
|
||||
<section ref="modsRow" class="instances">
|
||||
@@ -95,6 +89,7 @@ const handleRightPage = () => {
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
@@ -140,11 +135,14 @@ const handleRightPage = () => {
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-right: auto;
|
||||
margin-top: 0.8rem;
|
||||
scroll-behavior: smooth;
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
:deep(.instance-card-item) {
|
||||
margin-bottom: 0.1rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
background: transparent;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="text no-select">
|
||||
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
||||
</div>
|
||||
<p class="no-select">
|
||||
<p class="accounts-text no-select">
|
||||
<UsersIcon />
|
||||
Accounts
|
||||
</p>
|
||||
@@ -24,13 +24,13 @@
|
||||
<h4>{{ selectedAccount.username }}</h4>
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="logged-out account">
|
||||
<h4>Not signed in</h4>
|
||||
<Button icon-only color="primary" @click="login()">
|
||||
<Button v-tooltip="'Log in'" icon-only color="primary" @click="login()">
|
||||
<LogInIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -40,7 +40,7 @@
|
||||
<Avatar :src="account.profile_picture" class="icon" />
|
||||
<p>{{ account.username }}</p>
|
||||
</Button>
|
||||
<Button icon-only @click="logout(account.id)">
|
||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -79,7 +79,7 @@ const appendProfiles = (accounts) => {
|
||||
return accounts.map((account) => {
|
||||
return {
|
||||
...account,
|
||||
profile_picture: `https://crafthead.net/helm/${account.id.replace(/-/g, '')}/128`,
|
||||
profile_picture: `https://mc-heads.net/avatar/${account.id}/128`,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -187,6 +187,11 @@ onBeforeUnmount(() => {
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
@@ -287,4 +292,11 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accounts-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,13 +5,15 @@ import { ref } from 'vue'
|
||||
|
||||
const version = ref('')
|
||||
const title = ref('')
|
||||
const projectId = ref('')
|
||||
const icon = ref('')
|
||||
const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: (id, projectTitle, projectIcon) => {
|
||||
show: (id, projectId, projectTitle, projectIcon) => {
|
||||
version.value = id
|
||||
projectId.value = projectId
|
||||
title.value = projectTitle
|
||||
icon.value = projectIcon
|
||||
confirmModal.value.show()
|
||||
@@ -20,7 +22,7 @@ defineExpose({
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
await pack_install(version.value, title.value, icon.value ? icon.value : null)
|
||||
await pack_install(projectId.value, version.value, title.value, icon.value ? icon.value : null)
|
||||
confirmModal.value.hide()
|
||||
}
|
||||
</script>
|
||||
@@ -28,10 +30,8 @@ async function install() {
|
||||
<template>
|
||||
<Modal ref="confirmModal" header="Are you sure?">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This project is already installed on your system. Are you sure you want to install it again?
|
||||
</p>
|
||||
<div class="button-group">
|
||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||
<div class="input-group push-right">
|
||||
<Button @click="() => $refs.confirmModal.hide()"><XIcon />Cancel</Button>
|
||||
<Button color="primary" :disabled="installing" @click="install()"
|
||||
><DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}</Button
|
||||
@@ -48,11 +48,4 @@ async function install() {
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, useSlots } from 'vue'
|
||||
import { onUnmounted, ref, useSlots, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Card, DownloadIcon, XIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { run, list } from '@/helpers/profile'
|
||||
import {
|
||||
@@ -14,6 +13,8 @@ import {
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -29,10 +30,20 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const playing = ref(false)
|
||||
|
||||
const uuid = ref(null)
|
||||
const modLoading = ref(false)
|
||||
const modLoading = ref(
|
||||
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false
|
||||
)
|
||||
|
||||
watch(props.instance, () => {
|
||||
modLoading.value = props.instance.install_stage
|
||||
? props.instance.install_stage !== 'installed'
|
||||
: false
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const router = useRouter()
|
||||
@@ -74,21 +85,26 @@ const install = async (e) => {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.instance.project_id)
|
||||
) {
|
||||
try {
|
||||
modLoading.value = true
|
||||
await pack_install(versions[0].id, props.instance.title, props.instance.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
modLoading.value = false
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
modLoading.value = false
|
||||
}
|
||||
} else confirmModal.value.show(versions[0].id, props.instance.title, props.instance.icon_url)
|
||||
modLoading.value = true
|
||||
await pack_install(
|
||||
props.instance.project_id,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
props.instance.icon_url
|
||||
).catch(handleError)
|
||||
modLoading.value = false
|
||||
} else
|
||||
confirmModal.value.show(
|
||||
props.instance.project_id,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
props.instance.icon_url
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(props.instance.project_id, versions)
|
||||
}
|
||||
|
||||
modLoading.value = false
|
||||
// TODO: Add condition for installing a mod
|
||||
}
|
||||
|
||||
const play = async (e) => {
|
||||
@@ -103,21 +119,14 @@ const stop = async (e) => {
|
||||
e.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
try {
|
||||
// If we lost the uuid for some reason, such as a user navigating
|
||||
// from-then-back to this page, we will get all uuids by the instance path.
|
||||
// For-each uuid, kill the process.
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
|
||||
uuid.value = uuids[0]
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
|
||||
} catch (err) {
|
||||
// Theseus currently throws:
|
||||
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error
|
||||
// For now, we will catch and just warn
|
||||
console.warn(err)
|
||||
}
|
||||
// If we lost the uuid for some reason, such as a user navigating
|
||||
// from-then-back to this page, we will get all uuids by the instance path.
|
||||
// For-each uuid, kill the process.
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
|
||||
uuid.value = uuids[0]
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
|
||||
|
||||
uuid.value = null
|
||||
}
|
||||
@@ -206,9 +215,10 @@ onUnmounted(() => unlisten())
|
||||
>
|
||||
<XIcon />
|
||||
</div>
|
||||
<div v-else class="install cta buttonbase" @click="install"><DownloadIcon /></div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
</template>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -276,7 +286,7 @@ onUnmounted(() => unlisten())
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: 4.5rem;
|
||||
bottom: 5.5rem;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
@@ -312,11 +322,12 @@ onUnmounted(() => unlisten())
|
||||
z-index: 1;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: 1rem;
|
||||
bottom: 3.5rem;
|
||||
right: 1.25rem;
|
||||
bottom: 5rem;
|
||||
opacity: 0;
|
||||
transition: 0.3s ease-in-out bottom, 0.1s ease-in-out opacity !important;
|
||||
transition: 0.2s ease-in-out bottom, 0.1s ease-in-out opacity, 0.1s ease-in-out filter !important;
|
||||
cursor: pointer;
|
||||
box-shadow: var(--shadow-floating);
|
||||
|
||||
svg {
|
||||
color: var(--color-accent-contrast);
|
||||
@@ -324,11 +335,6 @@ onUnmounted(() => unlisten())
|
||||
height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: none !important; /* overrides button-base class */
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
|
||||
&.install {
|
||||
background: var(--color-brand);
|
||||
display: flex;
|
||||
@@ -400,6 +406,8 @@ onUnmounted(() => unlisten())
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Modal ref="modal" header="Create instance">
|
||||
<div v-if="showContent" class="modal-body">
|
||||
<div class="modal-body">
|
||||
<div class="image-upload">
|
||||
<Avatar :src="display_icon" size="md" :rounded="true" />
|
||||
<div class="image-input">
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Name</p>
|
||||
<input v-model="profile_name" class="text-input" type="text" />
|
||||
<input v-model="profile_name" autocomplete="off" class="text-input" type="text" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
@@ -25,7 +25,16 @@
|
||||
<div class="input-row">
|
||||
<p class="input-label">Game version</p>
|
||||
<div class="versions">
|
||||
<DropdownSelect v-model="game_version" :options="game_versions" render-up />
|
||||
<multiselect
|
||||
v-model="game_version"
|
||||
class="selector"
|
||||
:options="game_versions"
|
||||
:multiple="false"
|
||||
:searchable="true"
|
||||
placeholder="Select game version"
|
||||
open-direction="top"
|
||||
:show-labels="false"
|
||||
/>
|
||||
<Checkbox
|
||||
v-if="showAdvanced"
|
||||
v-model="showSnapshots"
|
||||
@@ -41,17 +50,21 @@
|
||||
<div v-if="showAdvanced && loader_version === 'other' && loader !== 'vanilla'">
|
||||
<div v-if="game_version" class="input-row">
|
||||
<p class="input-label">Select version</p>
|
||||
<DropdownSelect
|
||||
<multiselect
|
||||
v-model="specified_loader_version"
|
||||
class="selector"
|
||||
:options="selectable_versions"
|
||||
render-up
|
||||
:searchable="true"
|
||||
placeholder="Select loader version"
|
||||
open-direction="top"
|
||||
:show-labels="false"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="input-row">
|
||||
<p class="warning">Select a game version before you select a loader version</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="input-group push-right">
|
||||
<Button @click="toggle_advanced">
|
||||
<CodeIcon />
|
||||
{{ showAdvanced ? 'Hide advanced' : 'Show advanced' }}
|
||||
@@ -74,7 +87,6 @@ import {
|
||||
Avatar,
|
||||
Button,
|
||||
Chips,
|
||||
DropdownSelect,
|
||||
Modal,
|
||||
PlusIcon,
|
||||
UploadIcon,
|
||||
@@ -83,19 +95,24 @@ import {
|
||||
Checkbox,
|
||||
} from 'omorphia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata'
|
||||
import {
|
||||
get_game_versions,
|
||||
get_fabric_versions,
|
||||
get_forge_versions,
|
||||
get_quilt_versions,
|
||||
} from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
const loader = ref('vanilla')
|
||||
const loader_version = ref('stable')
|
||||
const specified_loader_version = ref('')
|
||||
const showContent = ref(false)
|
||||
const icon = ref(null)
|
||||
const display_icon = ref(null)
|
||||
const showAdvanced = ref(false)
|
||||
@@ -104,22 +121,17 @@ const showSnapshots = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
showContent.value = false
|
||||
modal.value.show()
|
||||
game_version.value = ''
|
||||
specified_loader_version.value = ''
|
||||
profile_name.value = ''
|
||||
creating.value = false
|
||||
showAdvanced.value = false
|
||||
showSnapshots.value = false
|
||||
loader.value = ''
|
||||
loader.value = 'vanilla'
|
||||
loader_version.value = 'stable'
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
|
||||
setTimeout(() => {
|
||||
showContent.value = true
|
||||
}, 100)
|
||||
modal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -138,53 +150,50 @@ const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loade
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
loaders.value.push('vanilla')
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const game_versions = computed(() => {
|
||||
return all_game_versions.value
|
||||
return all_game_versions.value.versions
|
||||
.filter((item) => {
|
||||
let defaultVal = item.version_type === 'release' || showSnapshots.value
|
||||
let defaultVal = item.type === 'release' || showSnapshots.value
|
||||
if (loader.value === 'fabric') {
|
||||
defaultVal &= fabric_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= fabric_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'forge') {
|
||||
defaultVal &= forge_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= forge_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
} else if (loader.value === 'quilt') {
|
||||
defaultVal &= quilt_versions.value.gameVersions.some((x) => item.version === x.id)
|
||||
defaultVal &= quilt_versions.value.gameVersions.some((x) => item.id === x.id)
|
||||
}
|
||||
|
||||
return defaultVal
|
||||
})
|
||||
.map((item) => item.version)
|
||||
.map((item) => item.id)
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return (
|
||||
profile_name.value && game_version.value && game_versions.value.includes(game_version.value)
|
||||
profile_name.value.trim() &&
|
||||
game_version.value &&
|
||||
game_versions.value.includes(game_version.value)
|
||||
)
|
||||
})
|
||||
|
||||
const create_instance = async () => {
|
||||
try {
|
||||
creating.value = true
|
||||
const loader_version_value =
|
||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||
creating.value = true
|
||||
const loader_version_value =
|
||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||
|
||||
await create(
|
||||
profile_name.value,
|
||||
game_version.value,
|
||||
loader.value,
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
create(
|
||||
profile_name.value,
|
||||
game_version.value,
|
||||
loader.value,
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
|
||||
modal.value.hide()
|
||||
creating.value = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
creating.value = false
|
||||
}
|
||||
modal.value.hide()
|
||||
creating.value = false
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
@@ -246,12 +255,6 @@ const toggle_advanced = () => {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.image-upload {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
@@ -277,4 +280,8 @@ const toggle_advanced = () => {
|
||||
:deep(button.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.selector {
|
||||
max-width: 20rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -143,9 +143,15 @@ const check_valid = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="installModal" header="Install mod to instance">
|
||||
<Modal ref="installModal" header="Install project to instance">
|
||||
<div class="modal-body">
|
||||
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="search"
|
||||
placeholder="Search for an instance"
|
||||
/>
|
||||
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
||||
<Button
|
||||
@@ -181,7 +187,13 @@ const check_valid = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-settings">
|
||||
<input v-model="name" type="text" placeholder="Name" class="creation-input" />
|
||||
<input
|
||||
v-model="name"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
class="creation-input"
|
||||
/>
|
||||
<Button :disabled="creatingInstance === true || !check_valid" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
@@ -189,7 +201,7 @@ const check_valid = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="footer">
|
||||
<div class="input-group push-right">
|
||||
<Button :color="showCreation ? '' : 'primary'" @click="toggleCreation()">
|
||||
<PlusIcon />
|
||||
{{ showCreation ? 'Hide New Instance' : 'Create new instance' }}
|
||||
@@ -302,12 +314,4 @@ const check_valid = computed(() => {
|
||||
.profile-image {
|
||||
--size: 2rem !important;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="table-cell table-text">
|
||||
<span>{{ javaInstall.version }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text">
|
||||
<div v-tooltip="javaInstall.path" class="table-cell table-text">
|
||||
<span>{{ javaInstall.path }}</span>
|
||||
</div>
|
||||
<div class="table-cell table-text manage">
|
||||
@@ -22,10 +22,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="chosenInstallOptions.length === 0" class="table-row entire-row">
|
||||
<div class="table-cell table-text">No JARS Found!</div>
|
||||
<div class="table-cell table-text">No java installations found!</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="input-group push-right">
|
||||
<Button @click="$refs.detectJavaModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
@@ -96,13 +96,6 @@ function setJavaInstall(javaInstall) {
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.manage {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<JavaDetectionModal ref="detectJavaModal" @submit="(val) => emit('update:modelValue', val)" />
|
||||
<div class="toggle-setting">
|
||||
<input
|
||||
autocomplete="off"
|
||||
:disabled="props.disabled"
|
||||
:value="props.modelValue ? props.modelValue.path : ''"
|
||||
type="text"
|
||||
@@ -28,30 +29,25 @@
|
||||
<FolderSearchIcon />
|
||||
Browse
|
||||
</Button>
|
||||
<Button :disabled="props.disabled" @click="testJava">
|
||||
<Button v-if="testingJava" disabled> Testing... </Button>
|
||||
<Button v-else-if="testingJavaSuccess === true">
|
||||
<CheckIcon class="test-success" />
|
||||
Success
|
||||
</Button>
|
||||
<Button v-else-if="testingJavaSuccess === false">
|
||||
<XIcon class="test-fail" />
|
||||
Failed
|
||||
</Button>
|
||||
<Button v-else :disabled="props.disabled" @click="testJava">
|
||||
<PlayIcon />
|
||||
Test
|
||||
</Button>
|
||||
<AnimatedLogo v-if="testingJava === true" class="testing-loader" />
|
||||
<CheckIcon
|
||||
v-else-if="testingJavaSuccess === true && testingJava === false"
|
||||
class="test-success"
|
||||
/>
|
||||
<XIcon v-else-if="testingJavaSuccess === false && testingJava === false" class="test-fail" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
SearchIcon,
|
||||
PlayIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
AnimatedLogo,
|
||||
FolderSearchIcon,
|
||||
} from 'omorphia'
|
||||
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, FolderSearchIcon } from 'omorphia'
|
||||
import { get_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
@@ -143,14 +139,3 @@ async function handleJavaFileInput() {
|
||||
color: var(--color-red);
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.testing-loader {
|
||||
height: 1rem !important;
|
||||
width: 1rem !important;
|
||||
|
||||
svg {
|
||||
height: inherit !important;
|
||||
width: inherit !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
<span class="running-text">
|
||||
{{ currentProcesses[0].metadata.name }}
|
||||
</span>
|
||||
<Button icon-only class="icon-button stop" @click="stop()">
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop()">
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button icon-only class="icon-button" @click="goToTerminal()">
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
<Button
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<div v-else class="status">
|
||||
<span class="circle stopped" />
|
||||
<span class="running-text"> No running profiles </span>
|
||||
<span class="running-text"> No running instances </span>
|
||||
<Button
|
||||
v-if="currentLoadingBars.length > 0"
|
||||
ref="infoButton"
|
||||
|
||||
@@ -140,7 +140,7 @@ async function install() {
|
||||
queuedVersionData = versions.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(props.instance.metadata.game_version) &&
|
||||
v.loaders.includes(props.instance.metadata.loader)
|
||||
(props.project.project_type !== 'mod' || v.loaders.includes(props.instance.metadata.loader))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -152,11 +152,19 @@ async function install() {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
await packInstall(queuedVersionData.id, props.project.title, props.project.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
await packInstall(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
).catch(handleError)
|
||||
} else {
|
||||
props.confirmModal.show(queuedVersionData.id)
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (props.instance) {
|
||||
|
||||
@@ -35,6 +35,8 @@ defineProps({
|
||||
svg {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
fill: var(--color-brand);
|
||||
color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,6 +28,10 @@ export async function authenticate_await_completion() {
|
||||
return await invoke('auth_authenticate_await_completion')
|
||||
}
|
||||
|
||||
export async function cancel_flow() {
|
||||
return await invoke('auth_cancel_flow')
|
||||
}
|
||||
|
||||
/// Refresh some credentials using Hydra, if needed
|
||||
/// user is UUID
|
||||
/// update_name is bool
|
||||
|
||||
@@ -6,5 +6,6 @@ export const useFetch = async (url, item) => {
|
||||
return await ofetch(url)
|
||||
} catch (err) {
|
||||
handleError({ message: `Error fetching ${item}` })
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Installs pack from a version ID
|
||||
export async function install(versionId, packTitle, packIcon) {
|
||||
return await invoke('pack_install_version_id', { versionId, packTitle, packIcon })
|
||||
export async function install(projectId, versionId, packTitle, packIcon) {
|
||||
return await invoke('pack_install_version_id', { projectId, versionId, packTitle, packIcon })
|
||||
}
|
||||
|
||||
// Installs pack from a path
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { add_project_from_version as installMod, check_installed } from '@/helpers/profile'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function showInFolder(path) {
|
||||
return await invoke('show_in_folder', { path })
|
||||
}
|
||||
|
||||
export const releaseColor = (releaseType) => {
|
||||
switch (releaseType) {
|
||||
@@ -17,11 +22,20 @@ export const releaseColor = (releaseType) => {
|
||||
|
||||
export const installVersionDependencies = async (profile, version) => {
|
||||
for (const dep of version.dependencies) {
|
||||
if (dep.dependency_type !== 'required') continue
|
||||
if (dep.version_id) {
|
||||
if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
await installMod(profile.path, dep.version_id)
|
||||
} else {
|
||||
if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
|
||||
if (
|
||||
dep.project_id &&
|
||||
(await check_installed(profile.path, dep.project_id).catch(handleError))
|
||||
)
|
||||
continue
|
||||
const depVersions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${dep.project_id}/version`,
|
||||
'dependency versions'
|
||||
|
||||
@@ -4,6 +4,7 @@ import App from '@/App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import 'omorphia/dist/style.css'
|
||||
import '@/assets/stylesheets/global.scss'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import { initialize_state } from '@/helpers/state'
|
||||
import loadCssMixin from './mixins/macCssFix.js'
|
||||
|
||||
@@ -150,6 +150,25 @@ const handleInstanceSwitch = async (value) => {
|
||||
searchStore.ignoreInstance = value
|
||||
await switchPage(1)
|
||||
}
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const values = [
|
||||
{ label: 'Data Packs', href: `/browse/datapack` },
|
||||
{ label: 'Shaders', href: `/browse/shader` },
|
||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||
]
|
||||
|
||||
if (searchStore.instanceContext) {
|
||||
if (searchStore.instanceContext.metadata.loader !== 'vanilla') {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
}
|
||||
} else {
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
||||
}
|
||||
|
||||
return values
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -160,7 +179,7 @@ const handleInstanceSwitch = async (value) => {
|
||||
<Checkbox
|
||||
:model-value="searchStore.ignoreInstance"
|
||||
:checked="searchStore.ignoreInstance"
|
||||
label="Unfilter loader & version"
|
||||
label="Show unsupported content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||
/>
|
||||
@@ -277,30 +296,14 @@ const handleInstanceSwitch = async (value) => {
|
||||
<div class="search">
|
||||
<Promotion class="promotion" />
|
||||
<Card class="project-type-container">
|
||||
<NavRow
|
||||
:links="
|
||||
searchStore.instanceContext
|
||||
? [
|
||||
{ label: 'Mods', href: `/browse/mod` },
|
||||
{ label: 'Datapacks', href: `/browse/datapack` },
|
||||
{ label: 'Shaders', href: `/browse/shader` },
|
||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||
]
|
||||
: [
|
||||
{ label: 'Modpacks', href: '/browse/modpack' },
|
||||
{ label: 'Mods', href: '/browse/mod' },
|
||||
{ label: 'Datapacks', href: '/browse/datapack' },
|
||||
{ label: 'Shaders', href: '/browse/shader' },
|
||||
{ label: 'Resource Packs', href: '/browse/resourcepack' },
|
||||
]
|
||||
"
|
||||
/>
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
</Card>
|
||||
<Card class="search-panel-container">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="searchStore.searchInput"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
|
||||
@@ -20,18 +20,20 @@ breadcrumbs.setRootContext({ name: 'Home', link: route.path })
|
||||
const recentInstances = shallowRef([])
|
||||
|
||||
const getInstances = async () => {
|
||||
filter.value = ''
|
||||
const profiles = await list(true).catch(handleError)
|
||||
recentInstances.value = Object.values(profiles)
|
||||
|
||||
const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id)
|
||||
excludeIds.forEach((id, index) => {
|
||||
filter.value += `NOT"project_id"="${id}"`
|
||||
if (index < excludeIds.length - 1) filter.value += ' AND '
|
||||
})
|
||||
let filters = []
|
||||
for (const instance of recentInstances.value) {
|
||||
if (instance.metadata.linked_data && instance.metadata.linked_data.project_id) {
|
||||
filters.push(`NOT"project_id"="${instance.metadata.linked_data.project_id}"`)
|
||||
}
|
||||
}
|
||||
filter.value = filters.join(' AND ')
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
console.log(filter.value)
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'featured modpacks'
|
||||
@@ -40,7 +42,7 @@ const getFeaturedModpacks = async () => {
|
||||
}
|
||||
const getFeaturedMods = async () => {
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows',
|
||||
'featured mods'
|
||||
)
|
||||
featuredMods.value = response.hits
|
||||
|
||||
@@ -53,15 +53,20 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Card class="theming">
|
||||
<h2>Display</h2>
|
||||
<div class="toggle-setting">
|
||||
<div class="description">
|
||||
<h3>Color theme</h3>
|
||||
<p>Change the global launcher color theme.</p>
|
||||
</div>
|
||||
<div class="settings-page">
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Display</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme">
|
||||
<span class="label__title">Color theme</span>
|
||||
<span class="label__description">Change the global launcher color theme.</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
id="theme"
|
||||
name="Theme dropdown"
|
||||
:options="themeStore.themeOptions"
|
||||
:default-value="settings.theme"
|
||||
@@ -75,12 +80,15 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
<div class="description">
|
||||
<h3>Collapsed navigation mode</h3>
|
||||
<p>Change the style of the side navigation bar</p>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="collapsed-nav">
|
||||
<span class="label__title">Collapsed navigation mode</span>
|
||||
<span class="label__description"
|
||||
>Change the style of the side navigation bar to a compact version.</span
|
||||
>
|
||||
</label>
|
||||
<Toggle
|
||||
id="collapsed-nav"
|
||||
:model-value="themeStore.collapsedNavigation"
|
||||
:checked="themeStore.collapsedNavigation"
|
||||
@update:model-value="
|
||||
@@ -92,116 +100,187 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Launcher settings</h2>
|
||||
<div class="settings-group">
|
||||
<h3>Resource management</h3>
|
||||
<div class="toggle-setting">
|
||||
<span>Maximum concurrent downloads</span>
|
||||
<Slider
|
||||
v-model="settings.max_concurrent_downloads"
|
||||
class="concurrent-downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
<span>Maximum concurrent writes</span>
|
||||
<Slider
|
||||
v-model="settings.max_concurrent_writes"
|
||||
class="concurrent-downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Resource management</span>
|
||||
</h3>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Java</h2>
|
||||
<div class="settings-group">
|
||||
<h3>Java 17 location</h3>
|
||||
<JavaSelector v-model="settings.java_globals.JAVA_17" :version="17" />
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Java 8 location</h3>
|
||||
<JavaSelector v-model="settings.java_globals.JAVA_8" :version="8" />
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Java arguments</h3>
|
||||
<input
|
||||
v-model="settings.javaArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="max-downloads">
|
||||
<span class="label__title">Maximum concurrent downloads</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can download at the same time. Set this to a
|
||||
lower value if you have a poor internet connection.</span
|
||||
>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-downloads"
|
||||
v-model="settings.max_concurrent_downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Environment variables</h3>
|
||||
<input
|
||||
v-model="settings.envArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
placeholder="Enter environment variables..."
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="max-writes">
|
||||
<span class="label__title">Maximum concurrent writes</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can write to the disk at once. Set this to a
|
||||
lower value if you are frequently getting I/O errors.</span
|
||||
>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-writes"
|
||||
v-model="settings.max_concurrent_writes"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Java settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<label for="java-17">
|
||||
<span class="label__title">Java 17 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-17" v-model="settings.java_globals.JAVA_17" :version="17" />
|
||||
<label for="java-8">
|
||||
<span class="label__title">Java 8 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-8" v-model="settings.java_globals.JAVA_8" :version="8" />
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Minimum memory
|
||||
<Slider v-model="settings.memory.minimum" :min="256" :max="maxMemory" :step="10" />
|
||||
<label for="java-args">
|
||||
<span class="label__title">Java arguments</span>
|
||||
</label>
|
||||
<input
|
||||
id="java-args"
|
||||
v-model="settings.javaArgs"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
/>
|
||||
<label for="env-vars">
|
||||
<span class="label__title">Environmental variables</span>
|
||||
</label>
|
||||
<input
|
||||
id="env-vars"
|
||||
v-model="settings.envArgs"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter environmental variables..."
|
||||
/>
|
||||
<hr class="card-divider" />
|
||||
<div class="adjacent-input">
|
||||
<label for="max-memory">
|
||||
<span class="label__title">Java memory</span>
|
||||
<span class="label__description">
|
||||
The memory allocated to each instance when it is ran.
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum memory
|
||||
<Slider v-model="settings.memory.maximum" :min="256" :max="maxMemory" :step="10" />
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<Slider
|
||||
id="max-memory"
|
||||
v-model="settings.memory.maximum"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Hooks</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Pre launch
|
||||
<input v-model="settings.hooks.pre_launch" type="text" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Wrapper
|
||||
<input v-model="settings.hooks.wrapper" type="text" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Post exit
|
||||
<input v-model="settings.hooks.post_exit" type="text" class="input" />
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Hooks</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="pre-launch">
|
||||
<span class="label__title">Pre launch</span>
|
||||
<span class="label__description"> Ran before the instance is launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="pre-launch"
|
||||
v-model="settings.hooks.pre_launch"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter pre-launch command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="wrapper">
|
||||
<span class="label__title">Wrapper</span>
|
||||
<span class="label__description"> Wrapper command for launching Minecraft. </span>
|
||||
</label>
|
||||
<input
|
||||
id="wrapper"
|
||||
v-model="settings.hooks.wrapper"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter wrapper command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="post-exit">
|
||||
<span class="label__title">Post exit</span>
|
||||
<span class="label__description"> Ran after the game closes. </span>
|
||||
</label>
|
||||
<input
|
||||
id="post-exit"
|
||||
v-model="settings.hooks.post_exit"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter post-exit command..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Window Size</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Width
|
||||
<input v-model="settings.game_resolution[0]" type="number" class="input" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Height
|
||||
<input v-model="settings.game_resolution[1]" type="number" class="input" />
|
||||
</div>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Window size</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="width">
|
||||
<span class="label__title">Width</span>
|
||||
<span class="label__description"> The width of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="width"
|
||||
v-model="settings.game_resolution[0]"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
placeholder="Enter width..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="height">
|
||||
<span class="label__title">Height</span>
|
||||
<span class="label__description"> The height of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="height"
|
||||
v-model="settings.game_resolution[1]"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="Enter height..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.concurrent-downloads {
|
||||
width: 80% !important;
|
||||
}
|
||||
|
||||
.slider-input {
|
||||
width: 5rem !important;
|
||||
flex-basis: 5rem !important;
|
||||
.settings-page {
|
||||
margin: 1rem 1rem 1rem 0;
|
||||
}
|
||||
|
||||
.installation-input {
|
||||
@@ -209,54 +288,11 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.theming,
|
||||
.settings-card {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.theming {
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.card-divider {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -48,9 +48,13 @@
|
||||
>
|
||||
Loading...
|
||||
</Button>
|
||||
<!--TODO: https://github.com/tauri-apps/tauri/issues/4062 -->
|
||||
<Button class="instance-button" icon-only @click="open({ defaultPath: instance.path })">
|
||||
<Button
|
||||
v-tooltip="'Open instance folder'"
|
||||
class="instance-button"
|
||||
@click="showInFolder(instance.path)"
|
||||
>
|
||||
<FolderOpenIcon />
|
||||
Folder
|
||||
</Button>
|
||||
</span>
|
||||
</Card>
|
||||
@@ -104,8 +108,8 @@ import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { handleError, useBreadcrumbs, useLoading, useSearch } from '@/store/state'
|
||||
import { showInFolder } from '@/helpers/utils.js'
|
||||
|
||||
const route = useRoute()
|
||||
const searchStore = useSearch()
|
||||
@@ -148,24 +152,17 @@ await checkProcess()
|
||||
|
||||
const stopInstance = async () => {
|
||||
playing.value = false
|
||||
|
||||
try {
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(instance.value.path).catch(handleError)
|
||||
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError)
|
||||
} catch (err) {
|
||||
// Theseus currently throws:
|
||||
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error
|
||||
// For now, we will catch and just warn
|
||||
console.warn(err)
|
||||
}
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(instance.value.path).catch(handleError)
|
||||
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError)
|
||||
}
|
||||
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
searchStore.instanceContext = instance.value
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -30,10 +30,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div ref="logContainer" class="log-text">
|
||||
<!-- {{ logs[1] }}-->
|
||||
<div v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
{{ line }}
|
||||
</div>
|
||||
<span v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
||||
{{ line }} <br />
|
||||
</span>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -210,5 +209,9 @@ onUnmounted(() => {
|
||||
overflow: auto;
|
||||
white-space: normal;
|
||||
color-scheme: dark;
|
||||
|
||||
.no-wrap {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,29 +3,55 @@
|
||||
<div class="card-row">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon />
|
||||
<input v-model="searchFilter" type="text" placeholder="Search Mods" class="text-input" />
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
type="text"
|
||||
:placeholder="`Search ${search.length} ${(['All', 'Other'].includes(selectedProjectType)
|
||||
? 'projects'
|
||||
: selectedProjectType.toLowerCase()
|
||||
).slice(0, search.length === 1 ? -1 : 64)}...`"
|
||||
class="text-input"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
<span class="manage">
|
||||
<span class="text-combo">
|
||||
<span class="no-wrap sort"> Sort By </span>
|
||||
<span class="no-wrap sort"> Sort by </span>
|
||||
<DropdownSelect
|
||||
v-model="sortFilter"
|
||||
name="sort-by"
|
||||
:options="['Name', 'Version', 'Author']"
|
||||
:options="['Name', 'Version', 'Author', 'Enabled']"
|
||||
default-value="Name"
|
||||
class="dropdown"
|
||||
/>
|
||||
</span>
|
||||
<Button color="primary" @click="router.push({ path: '/browse/mod' })">
|
||||
<Button
|
||||
color="primary"
|
||||
@click="
|
||||
router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
})
|
||||
"
|
||||
>
|
||||
<PlusIcon />
|
||||
<span class="no-wrap"> Add Content </span>
|
||||
<span class="no-wrap"> Add content </span>
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<Chips
|
||||
v-if="Object.keys(selectableProjectTypes).length > 1"
|
||||
v-model="selectedProjectType"
|
||||
:items="Object.keys(selectableProjectTypes)"
|
||||
/>
|
||||
<div class="table">
|
||||
<div class="table-row table-head">
|
||||
<div class="table-cell table-text">
|
||||
<Button icon-only :disabled="!projects.some((x) => x.outdated)" @click="updateAll">
|
||||
<Button
|
||||
v-tooltip="'Update all projects'"
|
||||
icon-only
|
||||
:disabled="!projects.some((x) => x.outdated)"
|
||||
@click="updateAll"
|
||||
>
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
</div>
|
||||
@@ -37,7 +63,13 @@
|
||||
<div v-for="mod in search" :key="mod.file_name" class="table-row">
|
||||
<div class="table-cell table-text">
|
||||
<AnimatedLogo v-if="mod.updating" class="btn icon-only updating-indicator"></AnimatedLogo>
|
||||
<Button v-else :disabled="!mod.outdated" icon-only @click="updateProject(mod)">
|
||||
<Button
|
||||
v-else
|
||||
v-tooltip="'Update project'"
|
||||
:disabled="!mod.outdated"
|
||||
icon-only
|
||||
@click="updateProject(mod)"
|
||||
>
|
||||
<UpdatedIcon v-if="mod.outdated" />
|
||||
<CheckIcon v-else />
|
||||
</Button>
|
||||
@@ -55,11 +87,12 @@
|
||||
<div class="table-cell table-text">{{ mod.version }}</div>
|
||||
<div class="table-cell table-text">{{ mod.author }}</div>
|
||||
<div class="table-cell table-text manage">
|
||||
<Button icon-only @click="removeMod(mod)">
|
||||
<Button v-tooltip="'Remove project'" icon-only @click="removeMod(mod)">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
<input
|
||||
id="switch-1"
|
||||
autocomplete="off"
|
||||
type="checkbox"
|
||||
class="switch stylized-toggle"
|
||||
:checked="!mod.disabled"
|
||||
@@ -82,6 +115,8 @@ import {
|
||||
UpdatedIcon,
|
||||
DropdownSelect,
|
||||
AnimatedLogo,
|
||||
Chips,
|
||||
formatProjectType,
|
||||
} from 'omorphia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
@@ -120,6 +155,7 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
disabled: project.disabled,
|
||||
updateVersion: project.metadata.update_version,
|
||||
outdated: !!project.metadata.update_version,
|
||||
project_type: project.metadata.project.project_type,
|
||||
})
|
||||
} else if (project.metadata.type === 'inferred') {
|
||||
projects.value.push({
|
||||
@@ -131,6 +167,7 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
icon: project.metadata.icon ? convertFileSrc(project.metadata.icon) : null,
|
||||
disabled: project.disabled,
|
||||
outdated: false,
|
||||
project_type: project.metadata.project_type,
|
||||
})
|
||||
} else {
|
||||
projects.value.push({
|
||||
@@ -142,16 +179,33 @@ for (const [path, project] of Object.entries(props.instance.projects)) {
|
||||
icon: null,
|
||||
disabled: project.disabled,
|
||||
outdated: false,
|
||||
project_type: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const searchFilter = ref('')
|
||||
const sortFilter = ref('')
|
||||
const selectedProjectType = ref('All')
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const obj = { All: 'all' }
|
||||
|
||||
for (const project of projects.value) {
|
||||
obj[project.project_type ? formatProjectType(project.project_type) + 's' : 'Other'] =
|
||||
project.project_type
|
||||
}
|
||||
|
||||
return obj
|
||||
})
|
||||
|
||||
const search = computed(() => {
|
||||
const projectType = selectableProjectTypes.value[selectedProjectType.value]
|
||||
const filtered = projects.value.filter((mod) => {
|
||||
return mod.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
return (
|
||||
mod.name.toLowerCase().includes(searchFilter.value.toLowerCase()) &&
|
||||
(projectType === 'all' || mod.project_type === projectType)
|
||||
)
|
||||
})
|
||||
|
||||
return updateSort(filtered, sortFilter.value)
|
||||
@@ -179,6 +233,16 @@ function updateSort(projects, sort) {
|
||||
}
|
||||
return 0
|
||||
})
|
||||
case 'Enabled':
|
||||
return projects.slice().sort((a, b) => {
|
||||
if (a.disabled && !b.disabled) {
|
||||
return 1
|
||||
}
|
||||
if (!a.disabled && b.disabled) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
default:
|
||||
return projects.slice().sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
@@ -230,6 +294,8 @@ async function updateProject(mod) {
|
||||
|
||||
async function toggleDisableMod(mod) {
|
||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||
console.log(mod.disabled)
|
||||
mod.disabled = !mod.disabled
|
||||
}
|
||||
|
||||
async function removeMod(mod) {
|
||||
@@ -248,6 +314,10 @@ async function removeMod(mod) {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
grid-template-columns: min-content 2fr 1fr 1fr 8rem;
|
||||
}
|
||||
@@ -284,14 +354,8 @@ async function removeMod(mod) {
|
||||
width: 7rem !important;
|
||||
}
|
||||
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.sort {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.sort {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@@ -300,4 +364,8 @@ async function removeMod(mod) {
|
||||
margin-left: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.v-popper--theme-tooltip .v-popper__inner {
|
||||
background: #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
<Chips v-model="loader" :items="loaders" />
|
||||
<Chips v-model="loader" :items="loaders" :never-empty="false" />
|
||||
</div>
|
||||
<div class="input-row">
|
||||
<p class="input-label">Game Version</p>
|
||||
@@ -22,7 +22,7 @@
|
||||
@change="(value) => (loaderVersionIndex = value.index)"
|
||||
/>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<div class="push-right input-group">
|
||||
<button class="btn" @click="$refs.changeVersionsModal.hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
@@ -41,7 +41,7 @@
|
||||
<section class="card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Profile</span>
|
||||
<span class="label__title size-card-header">Instance</span>
|
||||
</h3>
|
||||
</div>
|
||||
<label for="instance-icon">
|
||||
@@ -68,13 +68,13 @@
|
||||
<label for="project-name">
|
||||
<span class="label__title">Name</span>
|
||||
</label>
|
||||
<input id="profile-name" v-model="title" maxlength="80" type="text" />
|
||||
<input id="profile-name" v-model="title" autocomplete="off" maxlength="80" type="text" />
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="edit-versions">
|
||||
<span class="label__title">Edit mod loader/game versions</span>
|
||||
<span class="label__description">
|
||||
Allows you to change the mod loader, loader version, or game version of the profile.
|
||||
Allows you to change the mod loader, loader version, or game version of the instance.
|
||||
</span>
|
||||
</label>
|
||||
<button id="edit-versions" class="btn" @click="$refs.changeVersionsModal.show()">
|
||||
@@ -83,8 +83,12 @@
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Java</h2>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Java</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="settings-group">
|
||||
<h3>Installation</h3>
|
||||
<Checkbox v-model="overrideJavaInstall" label="Override global java installations" />
|
||||
@@ -95,10 +99,12 @@
|
||||
<h3>Java arguments</h3>
|
||||
<Checkbox v-model="overrideJavaArgs" label="Override global java arguments" />
|
||||
<input
|
||||
id="java-args"
|
||||
v-model="javaArgs"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideJavaArgs"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
class="installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
/>
|
||||
</div>
|
||||
@@ -107,98 +113,156 @@
|
||||
<Checkbox v-model="overrideEnvVars" label="Override global environment variables" />
|
||||
<input
|
||||
v-model="envVars"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideEnvVars"
|
||||
type="text"
|
||||
class="input installation-input"
|
||||
class="installation-input"
|
||||
placeholder="Enter environment variables..."
|
||||
/>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<div class="settings-group">
|
||||
<h3>Java memory</h3>
|
||||
<Checkbox v-model="overrideMemorySettings" label="Override global memory settings" />
|
||||
<div class="sliders">
|
||||
<span class="slider">
|
||||
Minimum memory
|
||||
<Slider
|
||||
v-model="memory.minimum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="10"
|
||||
/>
|
||||
<Slider
|
||||
v-model="memory.maximum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Window</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<Checkbox v-model="overrideWindowSettings" label="Override global window settings" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="width">
|
||||
<span class="label__title">Width</span>
|
||||
<span class="label__description"> The width of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="width"
|
||||
v-model="resolution[0]"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
placeholder="Enter width..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="height">
|
||||
<span class="label__title">Height</span>
|
||||
<span class="label__description"> The height of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="height"
|
||||
v-model="resolution[1]"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="Enter height..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Hooks</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<Checkbox v-model="overrideHooks" label="Override global hooks" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="pre-launch">
|
||||
<span class="label__title">Pre launch</span>
|
||||
<span class="label__description"> Ran before the instance is launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="pre-launch"
|
||||
v-model="hooks.pre_launch"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter pre-launch command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="wrapper">
|
||||
<span class="label__title">Wrapper</span>
|
||||
<span class="label__description"> Wrapper command for launching Minecraft. </span>
|
||||
</label>
|
||||
<input
|
||||
id="wrapper"
|
||||
v-model="hooks.wrapper"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter wrapper command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="post-exit">
|
||||
<span class="label__title">Post exit</span>
|
||||
<span class="label__description"> Ran after the game closes. </span>
|
||||
</label>
|
||||
<input
|
||||
id="post-exit"
|
||||
v-model="hooks.post_exit"
|
||||
autocomplete="off"
|
||||
:disabled="!overrideHooks"
|
||||
type="text"
|
||||
placeholder="Enter post-exit command..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Instance management</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="repair-profile">
|
||||
<span class="label__title">Repair instance</span>
|
||||
<span class="label__description">
|
||||
Reinstalls the instance and checks for corruption. Use this if your game is not launching
|
||||
due to launcher-related errors.
|
||||
</span>
|
||||
<span class="slider">
|
||||
Maximum memory
|
||||
<Slider
|
||||
v-model="memory.maximum"
|
||||
:disabled="!overrideMemorySettings"
|
||||
:min="256"
|
||||
:max="maxMemory"
|
||||
:step="10"
|
||||
/>
|
||||
</label>
|
||||
<button
|
||||
id="repair-profile"
|
||||
class="btn btn-highlight"
|
||||
:disabled="repairing"
|
||||
@click="repairProfile"
|
||||
>
|
||||
<HammerIcon /> Repair
|
||||
</button>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="delete-profile">
|
||||
<span class="label__title">Delete instance</span>
|
||||
<span class="label__description">
|
||||
Fully removes a instance from the disk. Be careful, as once you delete a instance there is
|
||||
no way to recover it.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Window</h2>
|
||||
<Checkbox v-model="overrideWindowSettings" label="Override global window settings" />
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Width
|
||||
<input
|
||||
v-model="resolution[0]"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
@change="updateProfile"
|
||||
/>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Height
|
||||
<input
|
||||
v-model="resolution[1]"
|
||||
:disabled="!overrideWindowSettings"
|
||||
type="number"
|
||||
class="input"
|
||||
@change="updateProfile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Hooks</h2>
|
||||
<Checkbox v-model="overrideHooks" label="Override global hooks" />
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Pre launch
|
||||
<input v-model="hooks.pre_launch" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Wrapper
|
||||
<input v-model="hooks.wrapper" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Post exit
|
||||
<input v-model="hooks.post_exit" :disabled="!overrideHooks" type="text" />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card class="settings-card">
|
||||
<h2 class="settings-title">Profile management</h2>
|
||||
<div class="settings-group">
|
||||
<div class="toggle-setting">
|
||||
Repair profile
|
||||
<button class="btn btn-highlight" :disabled="repairing" @click="repairProfile">
|
||||
<HammerIcon /> Repair
|
||||
</button>
|
||||
</div>
|
||||
<div class="toggle-setting">
|
||||
Delete profile
|
||||
<button class="btn btn-danger" :disabled="removing" @click="removeProfile">
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
<button
|
||||
id="delete-profile"
|
||||
class="btn btn-danger"
|
||||
:disabled="removing"
|
||||
@click="removeProfile"
|
||||
>
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
@@ -221,7 +285,7 @@ import {
|
||||
} from 'omorphia'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { edit, edit_icon, get_optimal_jre_key, install, remove } from '@/helpers/profile.js'
|
||||
import { computed, readonly, ref, shallowRef, watch } from 'vue'
|
||||
import { computed, onMounted, readonly, ref, shallowRef, watch } from 'vue'
|
||||
import { get_max_memory } from '@/helpers/jre.js'
|
||||
import { get } from '@/helpers/settings.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
@@ -388,10 +452,9 @@ const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loade
|
||||
.then(ref)
|
||||
.catch(handleError),
|
||||
])
|
||||
loaders.value.push('vanilla')
|
||||
loaders.value.unshift('vanilla')
|
||||
|
||||
const loader = ref(props.instance.metadata.loader)
|
||||
|
||||
const gameVersion = ref(props.instance.metadata.game_version)
|
||||
const selectableGameVersions = computed(() => {
|
||||
return all_game_versions.value
|
||||
@@ -457,6 +520,8 @@ async function saveGvLoaderEdits() {
|
||||
editing.value = false
|
||||
changeVersionsModal.value.hide()
|
||||
}
|
||||
|
||||
onMounted(() => console.log(loader.value))
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -478,73 +543,23 @@ async function saveGvLoaderEdits() {
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.input-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.installation-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sliders {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.slider {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-setting {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
background-color: var(--color-button-bg);
|
||||
border: none;
|
||||
color: var(--color-button-bg);
|
||||
height: 1px;
|
||||
margin: var(--gap-sm) 0;
|
||||
}
|
||||
|
||||
:deep(button.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
</Button>
|
||||
<a
|
||||
class="open btn icon-only"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/${data.project_type}/${data.slug}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="btn"
|
||||
>
|
||||
<ExternalIcon />
|
||||
@@ -97,7 +96,6 @@
|
||||
:href="data.issues_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<IssuesIcon aria-hidden="true" />
|
||||
<span>Issues</span>
|
||||
@@ -106,7 +104,6 @@
|
||||
v-if="data.source_url"
|
||||
:href="data.source_url"
|
||||
class="title"
|
||||
target="_blank"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
<CodeIcon aria-hidden="true" />
|
||||
@@ -117,7 +114,6 @@
|
||||
:href="data.wiki_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<WikiIcon aria-hidden="true" />
|
||||
<span>Wiki</span>
|
||||
@@ -127,7 +123,6 @@
|
||||
:href="data.wiki_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
target="_blank"
|
||||
>
|
||||
<DiscordIcon aria-hidden="true" />
|
||||
<span>Discord</span>
|
||||
@@ -136,7 +131,6 @@
|
||||
v-for="(donation, index) in data.donation_urls"
|
||||
:key="index"
|
||||
:href="donation.url"
|
||||
target="_blank"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
<BuyMeACoffeeIcon v-if="donation.id === 'bmac'" aria-hidden="true" />
|
||||
@@ -322,11 +316,19 @@ async function install(version) {
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === data.value.id)
|
||||
) {
|
||||
await packInstall(queuedVersionData.id, data.value.title, data.value.icon_url).catch(
|
||||
handleError
|
||||
)
|
||||
await packInstall(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url
|
||||
).catch(handleError)
|
||||
} else {
|
||||
confirmModal.value.show(queuedVersionData.id, data.value.title, data.value.icon_url)
|
||||
confirmModal.value.show(
|
||||
data.value.id,
|
||||
queuedVersionData.id,
|
||||
data.value.title,
|
||||
data.value.icon_url
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (instance.value) {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/mod/${route.params.id}/version/${route.params.version}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="btn"
|
||||
>
|
||||
<ExternalIcon />
|
||||
@@ -145,7 +144,6 @@
|
||||
<a
|
||||
:href="`https://modrinth.com/user/${author.user.username}`"
|
||||
rel="external"
|
||||
target="_blank"
|
||||
class="metadata-value btn author"
|
||||
>
|
||||
<Avatar size="sm" :src="author.user.avatar_url" circle />
|
||||
|
||||
@@ -105,6 +105,15 @@ export default new createRouter({
|
||||
breadcrumb: [{ name: '?Instance' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'projects/:type',
|
||||
name: 'ModsFilter',
|
||||
component: Instance.Mods,
|
||||
meta: {
|
||||
useRootContext: true,
|
||||
breadcrumb: [{ name: '?Instance' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'options',
|
||||
name: 'Options',
|
||||
@@ -128,4 +137,8 @@ export default new createRouter({
|
||||
],
|
||||
linkActiveClass: 'router-link-active',
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
scrollBehavior() {
|
||||
// always scroll to top
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user