-
+
❌
{{ language === 'en'
? 'The migration fix failed or had no effect.'
: 'Исправление миграции не было успешно применено или не имело эффекта.' }}
-
+
{{ language === 'en'
? 'If the problem persists, please try the other fix.'
: 'Если проблема сохраняется, пожалуйста, попробуйте другой способ.' }}
@@ -476,6 +484,7 @@ async function onApplyMigrationFix(eol) {
\ No newline at end of file
diff --git a/apps/app-frontend/src/helpers/update.js b/apps/app-frontend/src/helpers/update.js
index 6f3d87a13..cea6c6bd4 100644
--- a/apps/app-frontend/src/helpers/update.js
+++ b/apps/app-frontend/src/helpers/update.js
@@ -11,7 +11,7 @@ const releaseLink = `https://git.astralium.su/api/v1/repos/didirus/AstralRinth/r
const failedFetch = [`Failed to fetch remote releases:`, `Failed to fetch remote commits:`]
const osList = ['macos', 'windows', 'linux']
-const macExtensionList = ['.app', '.dmg']
+const macExtensionList = ['.dmg', '.pkg']
const windowsExtensionList = ['.exe', '.msi']
const blacklistPrefixes = [
diff --git a/apps/app/src/main.rs b/apps/app/src/main.rs
index dc3ee39e8..94427776a 100644
--- a/apps/app/src/main.rs
+++ b/apps/app/src/main.rs
@@ -157,7 +157,7 @@ fn main() {
*/
let _log_guard = theseus::start_logger();
- tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
+ tracing::info!("Initialized tracing subscriber. Loading AstralRinth App!");
let mut builder = tauri::Builder::default();
diff --git a/packages/app-lib/src/api/update.rs b/packages/app-lib/src/api/update.rs
index b1943dece..3ca0cebde 100644
--- a/packages/app-lib/src/api/update.rs
+++ b/packages/app-lib/src/api/update.rs
@@ -1,42 +1,117 @@
use reqwest;
+use std::path::PathBuf;
use tokio::fs::File as AsyncFile;
use tokio::io::AsyncWriteExt;
use tokio::process::Command;
-pub(crate) async fn download_file(download_url: &str, local_filename: &str, os_type: &str, auto_update_supported: bool) -> Result<(), Box> {
- let download_dir = dirs::download_dir().ok_or("[AR] • Failed to determine download directory")?;
+pub(crate) async fn get_resource(
+ download_url: &str,
+ local_filename: &str,
+ os_type: &str,
+ auto_update_supported: bool,
+) -> Result<(), Box> {
+ let download_dir = dirs::download_dir()
+ .ok_or("[AR] • Failed to determine download directory")?;
let full_path = download_dir.join(local_filename);
+
let response = reqwest::get(download_url).await?;
let bytes = response.bytes().await?;
let mut dest_file = AsyncFile::create(&full_path).await?;
dest_file.write_all(&bytes).await?;
- println!("[AR] • File downloaded to: {:?}", full_path);
+ tracing::info!("[AR] • File downloaded to: {:?}", full_path);
+
if auto_update_supported {
- let status;
- if os_type.to_lowercase() == "Windows".to_lowercase() {
- status = Command::new("explorer")
- .arg(download_dir.display().to_string())
- .status()
- .await
- .expect("[AR] • Failed to open downloads folder");
- } else if os_type.to_lowercase() == "MacOS".to_lowercase() {
- status = Command::new("open")
- .arg(full_path.to_str().unwrap_or_default())
- .status()
- .await
- .expect("[AR] • Failed to execute command");
- } else {
- status = Command::new(".")
- .arg(full_path.to_str().unwrap_or_default())
- .status()
- .await
- .expect("[AR] • Failed to execute command");
- }
- if status.success() {
- println!("[AR] • File opened successfully!");
- } else {
- eprintln!("[AR] • Failed to open the file. Exit code: {:?}", status.code());
+ let result = match os_type.to_lowercase().as_str() {
+ "windows" => handle_windows_file(&full_path).await,
+ "macos" => open_macos_file(&full_path).await,
+ _ => open_default(&full_path).await,
+ };
+
+ match result {
+ Ok(_) => tracing::info!("[AR] • File opened successfully!"),
+ Err(e) => tracing::info!("[AR] • Failed to open file: {e}"),
}
}
+
Ok(())
-}
\ No newline at end of file
+}
+
+async fn handle_windows_file(path: &PathBuf) -> Result<(), Box> {
+ let filename = path
+ .file_name()
+ .and_then(|f| f.to_str())
+ .unwrap_or_default()
+ .to_lowercase();
+
+ if filename.ends_with(".exe") || filename.ends_with(".msi") {
+ tracing::info!("[AR] • Detected installer: {}", filename);
+ run_windows_installer(path).await
+ } else {
+ open_windows_folder(path).await
+ }
+}
+
+async fn run_windows_installer(path: &PathBuf) -> Result<(), Box> {
+ let installer_path = path.to_str().unwrap_or_default();
+
+ let status = if installer_path.ends_with(".msi") {
+ Command::new("msiexec")
+ .args(&["/i", installer_path, "/quiet"])
+ .status()
+ .await?
+ } else {
+ Command::new("cmd")
+ .args(&["/C", installer_path])
+ .status()
+ .await?
+ };
+
+ if status.success() {
+ tracing::info!("[AR] • Installer started successfully.");
+ Ok(())
+ } else {
+ tracing::error!("Installer failed. Exit code: {:?}", status.code());
+ tracing::info!("[AR] • Trying to open folder...");
+ open_windows_folder(path).await
+ }
+}
+
+async fn open_windows_folder(path: &PathBuf) -> Result<(), Box> {
+ let folder = path.parent().unwrap_or(path);
+ let status = Command::new("explorer")
+ .arg(folder.display().to_string())
+ .status()
+ .await?;
+
+ if !status.success() {
+ Err(format!("Exit code: {:?}", status.code()).into())
+ } else {
+ Ok(())
+ }
+}
+
+async fn open_macos_file(path: &PathBuf) -> Result<(), Box> {
+ let status = Command::new("open")
+ .arg(path.to_str().unwrap_or_default())
+ .status()
+ .await?;
+
+ if !status.success() {
+ Err(format!("Exit code: {:?}", status.code()).into())
+ } else {
+ Ok(())
+ }
+}
+
+async fn open_default(path: &PathBuf) -> Result<(), Box> {
+ let status = Command::new(".")
+ .arg(path.to_str().unwrap_or_default())
+ .status()
+ .await?;
+
+ if !status.success() {
+ Err(format!("Exit code: {:?}", status.code()).into())
+ } else {
+ Ok(())
+ }
+}
diff --git a/packages/app-lib/src/state/db.rs b/packages/app-lib/src/state/db.rs
index bebcf5fb9..c939fd64a 100644
--- a/packages/app-lib/src/state/db.rs
+++ b/packages/app-lib/src/state/db.rs
@@ -1,5 +1,5 @@
-use crate::state::DirectoryInfo;
use crate::ErrorKind;
+use crate::state::DirectoryInfo;
use sqlx::sqlite::{
SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions,
};
@@ -78,23 +78,23 @@ Problem files, view detailed information in .gitattributes:
CRLF -> 4c47e326f16f2b1efca548076ce638d4c90dd610172fe48c47d6de9bc46ef1c5abeadfdea05041ddd72c3819fa10c040
LF -> e973512979feac07e415405291eefafc1ef0bd89454958ad66f5452c381db8679c20ffadab55194ecf6ba8ec4ca2db21
/packages/app-lib/migrations/20240813205023_drop-active-unique.sql !eol
-CRLF -> 10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9
+CRLF -> C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D
LF -> 5b53534a7ffd74eebede234222be47e1d37bd0cc5fee4475212491b0c0379c16e3079e08eee0af959b1fa20835eeb206
/packages/app-lib/migrations/20240930001852_disable-personalized-ads.sql !eol
-CRLF -> c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702
+CRLF -> C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57
LF -> c0de804f171b5530010edae087a6e75645c0e90177e28365f935c9fdd9a5c68e24850b8c1498e386a379d525d520bc57
/packages/app-lib/migrations/20241222013857_feature-flags.sql !eol
-CRLF -> f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09
+CRLF -> 6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE
LF -> c17542cb989a0466153e695bfa4717f8970feee185ca186a2caa1f2f6c5d4adb990ab97c26cacfbbe09c39ac81551704
*/
-pub(crate) async fn fix_version_hash(
- eol: &str,
-) -> crate::Result {
+pub(crate) async fn apply_migration_fix(eol: &str) -> crate::Result {
let started = Instant::now();
// Create connection to the database without migrations
let pool = connect_without_migrate().await?;
- tracing::info!("⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}");
+ tracing::info!(
+ "⚙️ Patching Modrinth corrupted migration checksums using EOL standard: {eol}"
+ );
// validate EOL input
if eol != "lf" && eol != "crlf" {
@@ -117,7 +117,7 @@ pub(crate) async fn fix_version_hash(
),
(
("crlf", "20240813205023"),
- "10f4a494df6fd791a093cc61401ecf3f9750fa6b97aa304ab06e29671e446586240910ffbf806f6ddc484a756770dde9",
+ "C8FD2EFE72E66E394732599EA8D93CE1ED337F098697B3ADAD40DD37CC6367893E199A8D7113B44A3D0FFB537692F91D",
),
(
("lf", "20240930001852"),
@@ -125,7 +125,7 @@ pub(crate) async fn fix_version_hash(
),
(
("crlf", "20240930001852"),
- "c8028ec3a2e61d15586e2f69ad6c6be5ac03b95918c2014cefb183ed6c254a52aad6f9ce98cda13ad545da3398574702",
+ "C0DE804F171B5530010EDAE087A6E75645C0E90177E28365F935C9FDD9A5C68E24850B8C1498E386A379D525D520BC57",
),
(
("lf", "20241222013857"),
@@ -133,7 +133,7 @@ pub(crate) async fn fix_version_hash(
),
(
("crlf", "20241222013857"),
- "f8c55065e2563fa4738976eb13a052ae4c28da8d33143185550f6e1cee394a3243b1dca090b3e8bc50a93a8286a78c09",
+ "6B6F097E5BB45A397C96C3F1DC9C2A18433564E81DB264FE08A4775198CCEAC03C9E63C3605994ECB19C281C37D8F6AE",
),
]);
diff --git a/packages/app-lib/src/util/utils.rs b/packages/app-lib/src/util/utils.rs
index 678ddb696..adebc3a67 100644
--- a/packages/app-lib/src/util/utils.rs
+++ b/packages/app-lib/src/util/utils.rs
@@ -26,7 +26,7 @@ pub fn read_package_json() -> io::Result {
pub async fn apply_migration_fix(eol: &str) -> Result {
tracing::info!("[AR] • Attempting to apply migration fix");
- let patched = db::fix_version_hash(eol).await?;
+ let patched = db::apply_migration_fix(eol).await?;
if patched {
tracing::info!("[AR] • Successfully applied migration fix");
} else {
@@ -43,7 +43,7 @@ pub async fn init_download(
) -> Result<()> {
println!("[AR] • Initialize downloading from • {:?}", download_url);
println!("[AR] • Save local file name • {:?}", local_filename);
- if let Err(e) = update::download_file(
+ if let Err(e) = update::get_resource(
download_url,
local_filename,
os_type,
diff --git a/packages/assets/styles/neon-button.scss b/packages/assets/styles/neon-button.scss
index 8fc133212..91e201f39 100644
--- a/packages/assets/styles/neon-button.scss
+++ b/packages/assets/styles/neon-button.scss
@@ -1,13 +1,8 @@
// [AR] Feature
-.btn-wrapper.neon :deep(:is(button, a, .button-like):first-child),
-.btn-wrapper.neon :slotted(:is(button, a, .button-like):first-child),
-.btn-wrapper.neon :slotted(*) > :is(button, a, .button-like):first-child,
-.btn-wrapper.neon :slotted(*) > *:first-child > :is(button, a, .button-like):first-child,
-.btn-wrapper.neon
- :slotted(*)
- > *:first-child
- > *:first-child
- > :is(button, a, .button-like):first-child {
+.neon-button.neon :deep(:is(button, a, .button-like)),
+.neon-button.neon :slotted(:is(button, a, .button-like)),
+.neon-button.neon :slotted(*) :is(button, a, .button-like) {
+ cursor: pointer;
background-color: transparent;
border: 1px solid #3e8cde;
color: #3e8cde;
@@ -22,20 +17,17 @@
box-shadow: 0 0 4px rgba(79, 173, 255, 0.5);
}
+.bordered {
+ border-radius: 12px;
+}
+
/* Hover */
-.btn-wrapper.neon
- :deep(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled),
-.btn-wrapper.neon
- :slotted(:is(button, a, .button-like):first-child):hover:not([disabled]):not(.disabled),
-.btn-wrapper.neon
- :slotted(*) > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled),
-.btn-wrapper.neon
- :slotted(*) > *:first-child > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled),
-.btn-wrapper.neon
- :slotted(*)
- > *:first-child
- > *:first-child
- > :is(button, a, .button-like):first-child:hover:not([disabled]):not(.disabled) {
+.neon-button.neon
+ :deep(:is(button, a, .button-like):hover):not([disabled]):not(.disabled),
+.neon-button.neon
+ :slotted(:is(button, a, .button-like):hover):not([disabled]):not(.disabled),
+.neon-button.neon
+ :slotted(*) :is(button, a, .button-like):hover:not([disabled]):not(.disabled) {
color: #10fae5;
transform: scale(1.02);
box-shadow:
diff --git a/packages/assets/styles/neon-icon.scss b/packages/assets/styles/neon-icon.scss
new file mode 100644
index 000000000..a0e2fb62f
--- /dev/null
+++ b/packages/assets/styles/neon-icon.scss
@@ -0,0 +1,37 @@
+// [AR] Feature
+.neon-icon {
+ background-color: transparent;
+ color: #3e8cde;
+ text-shadow:
+ 0 0 4px rgba(79, 173, 255, 0.5),
+ 0 0 8px rgba(14, 98, 204, 0.5),
+ 0 0 12px rgba(122, 31, 199, 0.5);
+ transition: transform 0.25s ease, color 0.25s ease, text-shadow 0.25s ease;
+ cursor: pointer;
+ display: inline-block;
+}
+
+/* Hover */
+.neon-icon:hover {
+ color: #10fae5;
+ transform: scale(1.05);
+ text-shadow:
+ 0 0 2px rgba(16, 250, 229, 0.4),
+ 0 0 4px rgba(16, 250, 229, 0.25);
+}
+
+.neon-icon.pulse {
+ position: relative;
+ animation: neon-pulse 1s ease-in-out infinite;
+ filter: drop-shadow(0 0 6px #10fae5);
+ box-shadow: none;
+}
+
+@keyframes neon-pulse {
+ 0%, 100% {
+ filter: drop-shadow(0 0 4px #10fae5);
+ }
+ 50% {
+ filter: drop-shadow(0 0 12px #10fae5);
+ }
+}
\ No newline at end of file
diff --git a/packages/assets/styles/neon-text.scss b/packages/assets/styles/neon-text.scss
new file mode 100644
index 000000000..1899f2b6c
--- /dev/null
+++ b/packages/assets/styles/neon-text.scss
@@ -0,0 +1,28 @@
+// [AR] Feature
+.neon-text {
+ background-color: transparent;
+ color: #3e8cde;
+ text-shadow:
+ 0 0 4px rgba(79, 173, 255, 0.5),
+ 0 0 8px rgba(14, 98, 204, 0.5),
+ 0 0 12px rgba(122, 31, 199, 0.5);
+ transition:
+ color 0.25s ease,
+ box-shadow 0.3s ease,
+ transform 0.15s ease;
+
+ white-space: normal;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ max-width: 100%;
+ display: inline-block;
+ padding: 4px 8px;
+}
+
+/* Hover */
+.neon-text:hover:not([disabled]):not(.disabled) {
+ color: #10fae5;
+ text-shadow:
+ 0 0 2px rgba(16, 250, 229, 0.4),
+ 0 0 4px rgba(16, 250, 229, 0.25);
+}