mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-21 18:02:32 +01:00
updater go brrrr
This commit is contained in:
parent
c7b7cf4cb7
commit
42c802c957
@ -25,6 +25,7 @@
|
||||
"@tauri-apps/plugin-dialog": "2.0.0-alpha.3",
|
||||
"@tauri-apps/plugin-log": "2.0.0-alpha.3",
|
||||
"@tauri-apps/plugin-notification": "2.0.0-alpha.3",
|
||||
"@tauri-apps/plugin-os": "2.0.0-alpha.6",
|
||||
"@tauri-apps/plugin-process": "2.0.0-alpha.3",
|
||||
"@tauri-apps/plugin-stronghold": "2.0.0-alpha.4",
|
||||
"@tauri-apps/plugin-updater": "2.0.0-alpha.3",
|
||||
|
@ -68,6 +68,9 @@ dependencies:
|
||||
'@tauri-apps/plugin-notification':
|
||||
specifier: 2.0.0-alpha.3
|
||||
version: 2.0.0-alpha.3
|
||||
'@tauri-apps/plugin-os':
|
||||
specifier: 2.0.0-alpha.6
|
||||
version: 2.0.0-alpha.6
|
||||
'@tauri-apps/plugin-process':
|
||||
specifier: 2.0.0-alpha.3
|
||||
version: 2.0.0-alpha.3
|
||||
@ -3516,6 +3519,11 @@ packages:
|
||||
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/api@2.0.0-alpha.13:
|
||||
resolution: {integrity: sha512-sGgCkFahF3OZAHoGN5Ozt9WK7wJlbVZSgWpPQKNag4nSOX1+Py6VDRTEWriiJHDiV+gg31CWHnNXRy6TFoZmdA==}
|
||||
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/cli-darwin-arm64@2.0.0-alpha.16:
|
||||
resolution: {integrity: sha512-T/yu8+m4XrI1Ja5aVnsv4v5aGqIvwz1egHarMgh4LXrlMioJ60BoxDPfenaUokO6NVee212woFSmH6p4S7V8PA==}
|
||||
engines: {node: '>= 10'}
|
||||
@ -3659,6 +3667,12 @@ packages:
|
||||
'@tauri-apps/api': 2.0.0-alpha.11
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-os@2.0.0-alpha.6:
|
||||
resolution: {integrity: sha512-ld5p56TiWxnGHCysEfHLmRH5+Lz7rzDRC5H5WzofVNPiqp4ra1O5y1lNOWklE01Rm5P1c0t1PbUVdNGpeYA3GQ==}
|
||||
dependencies:
|
||||
'@tauri-apps/api': 2.0.0-alpha.13
|
||||
dev: false
|
||||
|
||||
/@tauri-apps/plugin-process@2.0.0-alpha.3:
|
||||
resolution: {integrity: sha512-XfDBtjW5s584/OJvpaWo3EGtB/lRYltWUmdRotEs4CMWZRRidDfIWf39BWLEXOnik7aMZOA9LVURaTU2QHBy+g==}
|
||||
dependencies:
|
||||
|
48
src-tauri/Cargo.lock
generated
48
src-tauri/Cargo.lock
generated
@ -103,6 +103,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-os",
|
||||
"tauri-plugin-process",
|
||||
"tauri-plugin-updater",
|
||||
"url",
|
||||
@ -1399,6 +1400,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
@ -2436,6 +2447,17 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@ -3502,6 +3524,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
@ -3734,6 +3765,23 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-os"
|
||||
version = "2.0.0-alpha.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7cfaf07f8dcbfd4b2ce6156c4158d9d1419850ffe4e8146b6e890b5381e6906"
|
||||
dependencies = [
|
||||
"gethostname",
|
||||
"log",
|
||||
"os_info",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serialize-to-javascript",
|
||||
"sys-locale",
|
||||
"tauri",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-process"
|
||||
version = "2.0.0-alpha.6"
|
||||
|
@ -21,6 +21,7 @@ tauri = { version = "2.0.0-alpha", features = ["devtools", "tray-icon"] }
|
||||
tauri-plugin-updater = "2.0.0-alpha"
|
||||
tauri-plugin-process = "2.0.0-alpha"
|
||||
tauri-plugin-log = "2.0.0-alpha"
|
||||
tauri-plugin-os = "2.0.0-alpha"
|
||||
reqwest = { version = "0.11.22", features = ["json"] }
|
||||
url = "2.4.1"
|
||||
chrono = "0.4"
|
||||
|
@ -32,6 +32,7 @@ pub fn run() {
|
||||
|
||||
let mut app = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_process::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
// Add logging plugin
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
@ -39,11 +40,11 @@ pub fn run() {
|
||||
.targets([
|
||||
Target::new(TargetKind::Webview),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some("webview.log".into()),
|
||||
file_name: Some("webview".into()),
|
||||
})
|
||||
.filter(|metadata| metadata.target() == WEBVIEW_TARGET),
|
||||
Target::new(TargetKind::LogDir {
|
||||
file_name: Some("rust.log".into()),
|
||||
file_name: Some("rust".into()),
|
||||
})
|
||||
.filter(|metadata| metadata.target() != WEBVIEW_TARGET),
|
||||
])
|
||||
@ -90,7 +91,9 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
close_splashscreen,
|
||||
updater::check_for_updates,
|
||||
updater::install_update
|
||||
updater::download_update,
|
||||
updater::install_update,
|
||||
updater::clear_update_cache
|
||||
])
|
||||
.build(context)
|
||||
.expect("error while running tauri application");
|
||||
|
@ -33,14 +33,24 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
return;
|
||||
}
|
||||
|
||||
match window.emit("CHECKING_FOR_UPDATE", Some(serde_json::json!({}))) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to emit update checking event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
println!("Searching for update file on github.");
|
||||
println!("[Updater] Searching for update file on github.");
|
||||
// Custom configure the updater.
|
||||
let github_releases_endpoint = "https://api.github.com/repos/spacebarchat/client/releases";
|
||||
let github_releases_endpoint = match Url::parse(github_releases_endpoint) {
|
||||
Ok(url) => url,
|
||||
Err(e) => {
|
||||
println!("Failed to parse url: {:?}. Failed to check for updates", e);
|
||||
println!(
|
||||
"[Updater] Failed to parse url: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
@ -54,7 +64,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to send request: {:?}. Failed to check for updates",
|
||||
"[Updater] Failed to send request: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
@ -63,7 +73,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
|
||||
if response.status() != reqwest::StatusCode::OK {
|
||||
println!(
|
||||
"Non OK status code: {:?}. Failed to check for updates",
|
||||
"[Updater] Non OK status code: {:?}. Failed to check for updates",
|
||||
response.status()
|
||||
);
|
||||
return;
|
||||
@ -72,7 +82,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
Ok(releases) => releases,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to parse response: {:?}. Failed to check for updates",
|
||||
"[Updater] Failed to parse response: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
@ -81,7 +91,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
|
||||
// check if there are any releases
|
||||
if releases.len() == 0 {
|
||||
println!("No releases found. Failed to check for updates");
|
||||
println!("[Updater] No releases found. Failed to check for updates");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -102,7 +112,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
let tauri_release_asset = match tauri_release_asset {
|
||||
Some(tauri_release_asset) => tauri_release_asset,
|
||||
None => {
|
||||
println!("Failed to find latest.json asset. Failed to check for updates\n\nFound Assets are:");
|
||||
println!("[Updater] Failed to find latest.json asset. Failed to check for updates\n\nFound Assets are:");
|
||||
// Print a list of the assets found
|
||||
for asset in latest_release.assets.iter() {
|
||||
println!(" {:?}", asset.name);
|
||||
@ -114,23 +124,29 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
let tauri_release_endpoint = match Url::parse(&tauri_release_asset.browser_download_url) {
|
||||
Ok(url) => url,
|
||||
Err(e) => {
|
||||
println!("Failed to parse url: {:?}. Failed to check for updates", e);
|
||||
println!(
|
||||
"[Updater] Failed to parse url: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let updater_builder = match handle
|
||||
.updater_builder()
|
||||
.version_comparator(|current_version, latest_version| {
|
||||
println!("Current version: {}", current_version);
|
||||
println!("Latest version: {}", latest_version.version.clone());
|
||||
println!("[Updater] Current version: {}", current_version);
|
||||
println!(
|
||||
"[Updater] Latest version: {}",
|
||||
latest_version.version.clone()
|
||||
);
|
||||
|
||||
if latest_version.version > current_version {
|
||||
println!("Latest version is greater than current version. ");
|
||||
println!("[Updater] Latest version is greater than current version. ");
|
||||
return true;
|
||||
}
|
||||
|
||||
if latest_version.version < current_version {
|
||||
println!("Latest version is lower than current version. ");
|
||||
println!("[Updater] Latest version is lower than current version. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -142,7 +158,7 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
Ok(updater_builder) => updater_builder,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to build updater builder: {:?}. Failed to check for updates",
|
||||
"[Updater] Failed to build updater builder: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
@ -153,18 +169,18 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
Ok(updater) => updater,
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to build updater: {:?}. Failed to check for updates",
|
||||
"[Updater] Failed to build updater: {:?}. Failed to check for updates",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("Checking for updates");
|
||||
println!("[Updater] Checking for updates");
|
||||
|
||||
let response = updater.check().await;
|
||||
|
||||
println!("Update check response: {:?}", response);
|
||||
println!("[Updater] Update check response: {:?}", response);
|
||||
|
||||
match response {
|
||||
Ok(Some(update)) => {
|
||||
@ -174,40 +190,198 @@ pub fn check_for_updates<R: Runtime>(ignore_prereleases: bool, window: tauri::Wi
|
||||
// }
|
||||
UPDATE_INFO.lock().unwrap().replace(update.clone());
|
||||
|
||||
match window.emit(
|
||||
"UPDATE_AVAILABLE",
|
||||
Some(UpdateAvailable {
|
||||
version: update.version,
|
||||
body: update.body,
|
||||
}),
|
||||
) {
|
||||
let package_path = handle
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("update.sbcup");
|
||||
|
||||
// if we already have an update package, emit the update downloaded event
|
||||
if package_path.exists() {
|
||||
match window.emit(
|
||||
"UPDATE_DOWNLOADED",
|
||||
Some(UpdateAvailable {
|
||||
version: update.version.clone(),
|
||||
body: update.body.clone(),
|
||||
}),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to emit update downloaded event: {:?}", e);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise emit the update available event
|
||||
match window.emit("UPDATE_AVAILABLE", Some({})) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("Failed to emit update available event: {:?}", e);
|
||||
println!("[Updater] Failed to emit update available event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
return download_update(window).await;
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("[Updater] No update available");
|
||||
match window.emit("UPDATE_NOT_AVAILABLE", Some({})) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"[Updater] Failed to emit update not available event: {:?}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to check for updates: {:?}.", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_update<R: Runtime>(_window: tauri::Window<R>) {
|
||||
println!("Downloading and installing update!");
|
||||
pub async fn download_update<R: Runtime>(window: tauri::Window<R>) {
|
||||
println!("[Updater] Downloading update package");
|
||||
|
||||
let update = match UPDATE_INFO.lock().unwrap().clone() {
|
||||
Some(update) => update,
|
||||
None => {
|
||||
println!("No update found to install");
|
||||
println!("[Updater] No update found to download");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let install_response = update.download_and_install(|_, _| {}, || {}).await;
|
||||
if let Err(e) = install_response {
|
||||
println!("Failed to install update: {:?}", e);
|
||||
// emit UPDATE_DOWNLOADING
|
||||
match window.emit("UPDATE_DOWNLOADING", Some({})) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to emit update downloading event: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let on_chunk = |size: usize, progress: Option<u64>| {
|
||||
println!(
|
||||
"[Updater] Received chunk: size={}, progress={:?}",
|
||||
size, progress
|
||||
);
|
||||
};
|
||||
|
||||
let on_download_finish = || {
|
||||
println!("[Updater] Download finished!");
|
||||
};
|
||||
|
||||
let download_response = update.download(on_chunk, on_download_finish).await;
|
||||
|
||||
if let Err(e) = download_response {
|
||||
println!("[Updater] Failed to download update: {:?}", e);
|
||||
} else {
|
||||
println!("Update installed");
|
||||
println!("[Updater] Update downloaded");
|
||||
|
||||
let handle = window.app_handle().clone();
|
||||
let package_path = handle
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("update.sbcup");
|
||||
println!("[Updater] Saving update package to {:?}", package_path);
|
||||
|
||||
// store download_response bytes to a file
|
||||
match std::fs::write(package_path.clone(), download_response.unwrap()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to save update package: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match window.emit(
|
||||
"UPDATE_DOWNLOADED",
|
||||
Some(UpdateAvailable {
|
||||
version: update.version.clone(),
|
||||
body: update.body.clone(),
|
||||
}),
|
||||
) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to emit update downloaded event: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_update<R: Runtime>(window: tauri::Window<R>) {
|
||||
println!("[Updater] Installing update package");
|
||||
|
||||
let update = match UPDATE_INFO.lock().unwrap().clone() {
|
||||
Some(update) => update,
|
||||
None => {
|
||||
println!("[Updater] No update found to install");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let handle = window.app_handle().clone();
|
||||
let package_path = handle
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("update.sbcup");
|
||||
|
||||
// check if the update package exists
|
||||
if !package_path.exists() {
|
||||
println!("[Updater] No pending update found to install");
|
||||
return;
|
||||
}
|
||||
|
||||
// read in the update package bytes
|
||||
let bytes = match std::fs::read(package_path.clone()) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to read update package: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let install_response = update.install(bytes);
|
||||
|
||||
if let Err(e) = install_response {
|
||||
println!("[Updater] Failed to install update: {:?}", e);
|
||||
} else {
|
||||
println!("[Updater] Update installed");
|
||||
|
||||
// remove the update package
|
||||
match std::fs::remove_file(package_path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to remove update package: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn clear_update_cache<R: Runtime>(window: tauri::Window<R>) {
|
||||
let handle = window.app_handle().clone();
|
||||
let package_path = handle
|
||||
.path()
|
||||
.app_local_data_dir()
|
||||
.unwrap()
|
||||
.join("update.sbcup");
|
||||
|
||||
// check if the update package exists
|
||||
if !package_path.exists() {
|
||||
println!("[Updater] No pending update found to clear");
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the update package
|
||||
match std::fs::remove_file(package_path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("[Updater] Failed to remove update package: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ function App() {
|
||||
);
|
||||
|
||||
Globals.load();
|
||||
app.loadToken();
|
||||
app.loadSettings();
|
||||
|
||||
logger.debug("Loading complete");
|
||||
app.setAppLoading(false);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
import { modalController } from "../controllers/modals";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
import User from "../stores/objects/User";
|
||||
import Avatar from "./Avatar";
|
||||
@ -70,7 +71,11 @@ const ActionsWrapper = styled.div`
|
||||
function UserPanel() {
|
||||
const app = useAppStore();
|
||||
|
||||
const openSettingsModal = () => {};
|
||||
const openSettingsModal = () => {
|
||||
modalController.push({
|
||||
type: "settings",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Floating
|
||||
|
@ -14,17 +14,14 @@ const IconButton = styled.button`
|
||||
padding: 0;
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
color: red;
|
||||
}
|
||||
`;
|
||||
|
||||
const CustomIcon = styled(Icon)<{ $active?: boolean }>`
|
||||
color: ${(props) => (props.$active ? "#ffffff" : "var(--text-secondary)")};
|
||||
color: ${(props) => (props.$active ? "var(--text)" : "var(--text-secondary)")};
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
color: ${(props) => (props.$active ? "var(--text-secondary)" : "var(--text)")};
|
||||
cursor: pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
@ -115,9 +112,11 @@ interface ActionItemProps {
|
||||
ariaLabel?: string;
|
||||
tooltip: string;
|
||||
onClick?: () => void;
|
||||
disabled?: boolean;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
function ActionItem({ icon, active, ariaLabel, tooltip, onClick }: ActionItemProps) {
|
||||
function ActionItem({ icon, active, ariaLabel, tooltip, disabled, color, onClick }: ActionItemProps) {
|
||||
const logger = useLogger("ChatHeader.tsx:ActionItem");
|
||||
|
||||
return (
|
||||
@ -131,7 +130,13 @@ function ActionItem({ icon, active, ariaLabel, tooltip, onClick }: ActionItemPro
|
||||
<FloatingTrigger>
|
||||
<IconWrapper>
|
||||
<IconButton onClick={onClick}>
|
||||
<CustomIcon $active={active} icon={icon} size="24px" aria-label={ariaLabel} />
|
||||
<CustomIcon
|
||||
$active={!disabled && active}
|
||||
icon={icon}
|
||||
size="24px"
|
||||
aria-label={ariaLabel}
|
||||
color={color}
|
||||
/>
|
||||
</IconButton>
|
||||
</IconWrapper>
|
||||
</FloatingTrigger>
|
||||
@ -143,7 +148,7 @@ function ActionItem({ icon, active, ariaLabel, tooltip, onClick }: ActionItemPro
|
||||
* Top header for channel messages section
|
||||
*/
|
||||
function ChatHeader({ channel }: Props) {
|
||||
const { memberListVisible, toggleMemberList } = useAppStore();
|
||||
const { memberListVisible, toggleMemberList, updaterStore } = useAppStore();
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -153,6 +158,36 @@ function ChatHeader({ channel }: Props) {
|
||||
<ChannelTopic channel={channel} />
|
||||
{/* Action Items */}
|
||||
<ActionItemsWrapper>
|
||||
{updaterStore?.checkingForUpdates && (
|
||||
<ActionItem
|
||||
icon="mdiCloudSync"
|
||||
tooltip="Checking for Updates"
|
||||
ariaLabel="Checking for Updates"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
{updaterStore?.updateAvailable && (
|
||||
<ActionItem icon="mdiUpdate" tooltip="Update Available" ariaLabel="Upate Available" disabled />
|
||||
)}
|
||||
{updaterStore?.updateDownloading && (
|
||||
<ActionItem
|
||||
icon="mdiCloudDownload"
|
||||
tooltip="Downloading Update"
|
||||
ariaLabel="Downloading Update"
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
{updaterStore?.updateDownloaded && (
|
||||
<ActionItem
|
||||
icon="mdiDownload"
|
||||
tooltip="Update Ready!"
|
||||
ariaLabel="Update Ready!"
|
||||
color="var(--success)"
|
||||
onClick={() => {
|
||||
updaterStore.quitAndInstall();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* <ActionItem icon="mdiPound" ariaLabel="Threads" /> */}
|
||||
<DummySearch>
|
||||
<span>Search</span>
|
||||
|
@ -1,3 +1,143 @@
|
||||
export function SettingsModal() {
|
||||
return null;
|
||||
import { FormControlLabel, FormGroup, Switch } from "@mui/material";
|
||||
import { getTauriVersion, getVersion } from "@tauri-apps/api/app";
|
||||
import { arch, locale, platform, version } from "@tauri-apps/plugin-os";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { ModalProps } from "../../controllers/modals";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { isTauri } from "../../utils/Utils";
|
||||
import { GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
|
||||
import Button from "../Button";
|
||||
import Link from "../Link";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding: 16px 0;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ActionWrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const VersionWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: text;
|
||||
|
||||
& > span {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
`;
|
||||
|
||||
interface VersionInfo {
|
||||
tauri: string;
|
||||
app: string;
|
||||
platform: {
|
||||
name: string;
|
||||
arch: string;
|
||||
version: string;
|
||||
locale: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export const SettingsModal = observer(({ ...props }: ModalProps<"settings">) => {
|
||||
const app = useAppStore();
|
||||
const [versionInfo, setVersionInfo] = React.useState<VersionInfo | undefined>(undefined);
|
||||
|
||||
const getVersionInfo = React.useMemo(
|
||||
() => async () => {
|
||||
const [tauriVersion, appVersion, platformName, platformArch, platformVersion, platformLocale] =
|
||||
await Promise.all([getTauriVersion(), getVersion(), platform(), arch(), version(), locale()]);
|
||||
|
||||
setVersionInfo({
|
||||
tauri: tauriVersion,
|
||||
app: appVersion,
|
||||
platform: {
|
||||
name: platformName,
|
||||
arch: platformArch,
|
||||
version: platformVersion,
|
||||
locale: platformLocale,
|
||||
},
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isTauri) {
|
||||
getVersionInfo();
|
||||
}
|
||||
}, [getVersionInfo]);
|
||||
|
||||
return (
|
||||
<Modal {...props}>
|
||||
<Wrapper>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={app.fpsShown} onChange={(e) => app.setFpsShown(e.target.checked)} />}
|
||||
label="Show FPS Graph"
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{isTauri && (
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={app.updaterStore?.enabled}
|
||||
onChange={(e) => app.updaterStore?.setEnabled(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label="Enabled auto updater"
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
|
||||
<VersionWrapper>
|
||||
<span>
|
||||
Client Version:{" "}
|
||||
<Link href={`${REPO_URL}/commit/${GIT_REVISION}`} target="_blank" rel="noreferrer">
|
||||
{GIT_REVISION.substring(0, 7)}
|
||||
</Link>
|
||||
{` `}
|
||||
<Link
|
||||
href={GIT_BRANCH !== "DETACHED" ? `${REPO_URL}/tree/${GIT_BRANCH}` : undefined}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
({GIT_BRANCH})
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
{isTauri && (
|
||||
<>
|
||||
<span>App Version: {versionInfo?.app ?? "Fetching version information..."}</span>
|
||||
<span>Tauri Version: {versionInfo?.tauri ?? "Fetching version information..."}</span>
|
||||
<span>Platform: {versionInfo?.platform.name}</span>
|
||||
<span>Arch: {versionInfo?.platform.arch}</span>
|
||||
<span>OS Version: {versionInfo?.platform.version}</span>
|
||||
<span>Locale: {versionInfo?.platform.locale ?? "Unknown"}</span>
|
||||
</>
|
||||
)}
|
||||
</VersionWrapper>
|
||||
|
||||
<ActionWrapper>
|
||||
<Button
|
||||
palette="danger"
|
||||
onClick={() => {
|
||||
app.logout();
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</ActionWrapper>
|
||||
</Wrapper>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
JoinServerModal,
|
||||
KickMemberModal,
|
||||
LeaveServerModal,
|
||||
SettingsModal,
|
||||
} from "../../components/modals";
|
||||
import { Modal } from "./types";
|
||||
|
||||
@ -184,4 +185,5 @@ export const modalController = new ModalControllerExtended({
|
||||
// report_success: ReportSuccess,
|
||||
// modify_displayname: ModifyDisplayname,
|
||||
// changelog_usernames: ChangelogUsernames,
|
||||
settings: SettingsModal,
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ export type Modal = {
|
||||
key?: string;
|
||||
} & (
|
||||
| {
|
||||
type: "add_server" | "create_server" | "join_server";
|
||||
type: "add_server" | "create_server" | "join_server" | "settings";
|
||||
}
|
||||
| {
|
||||
type: "error";
|
||||
|
@ -3,6 +3,7 @@ import { action, computed, makeAutoObservable, observable } from "mobx";
|
||||
import secureLocalStorage from "react-secure-storage";
|
||||
import Logger from "../utils/Logger";
|
||||
import REST from "../utils/REST";
|
||||
import { isTauri } from "../utils/Utils";
|
||||
import AccountStore from "./AccountStore";
|
||||
import ChannelStore from "./ChannelStore";
|
||||
import ExperimentsStore from "./ExperimentsStore";
|
||||
@ -13,6 +14,7 @@ import PresenceStore from "./PresenceStore";
|
||||
import PrivateChannelStore from "./PrivateChannelStore";
|
||||
import RoleStore from "./RoleStore";
|
||||
import ThemeStore from "./ThemeStore";
|
||||
import UpdaterStore from "./UpdaterStore";
|
||||
import UserStore from "./UserStore";
|
||||
import Channel from "./objects/Channel";
|
||||
import Guild from "./objects/Guild";
|
||||
@ -46,6 +48,8 @@ export default class AppStore {
|
||||
@observable experiments = new ExperimentsStore();
|
||||
@observable presences = new PresenceStore(this);
|
||||
@observable queue = new MessageQueue(this);
|
||||
@observable updaterStore: UpdaterStore | null = null;
|
||||
|
||||
@observable activeGuild: Guild | null = null;
|
||||
@observable activeGuildId: Snowflake | undefined | "@me" = "@me";
|
||||
@observable activeChannel: Channel | null = null;
|
||||
@ -55,6 +59,10 @@ export default class AppStore {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
|
||||
if (isTauri) {
|
||||
this.updaterStore = new UpdaterStore(this);
|
||||
}
|
||||
|
||||
// bind this in toggleMemberList
|
||||
this.toggleMemberList = this.toggleMemberList.bind(this);
|
||||
// bind this in windowToggleFps
|
||||
@ -77,43 +85,11 @@ export default class AppStore {
|
||||
this.isAppLoading = value;
|
||||
}
|
||||
|
||||
@action
|
||||
setToken(token: string, save = false) {
|
||||
this.token = token;
|
||||
this.tokenLoaded = true;
|
||||
if (save) {
|
||||
secureLocalStorage.setItem("token", token);
|
||||
this.logger.info("Token saved to storage");
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
setUser(user: APIUser) {
|
||||
this.account = new AccountStore(user);
|
||||
}
|
||||
|
||||
@action
|
||||
loadToken() {
|
||||
const token = secureLocalStorage.getItem("token") as string | null;
|
||||
|
||||
this.tokenLoaded = true;
|
||||
|
||||
if (token) {
|
||||
this.logger.debug("Loaded token from storage.");
|
||||
this.setToken(token);
|
||||
} else {
|
||||
this.logger.debug("No token found in storage.");
|
||||
this.setGatewayReady(true);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
logout() {
|
||||
this.token = null;
|
||||
this.tokenLoaded = false;
|
||||
secureLocalStorage.removeItem("token");
|
||||
}
|
||||
|
||||
@action
|
||||
setNetworkConnected(value: boolean) {
|
||||
this.isNetworkConnected = value;
|
||||
@ -143,11 +119,6 @@ export default class AppStore {
|
||||
this.activeChannel = (id ? this.channels.get(id) : null) ?? null;
|
||||
}
|
||||
|
||||
@action
|
||||
setFpsShown(value: boolean) {
|
||||
this.fpsShown = value;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleMemberList() {
|
||||
this.memberListVisible = !this.memberListVisible;
|
||||
@ -157,6 +128,73 @@ export default class AppStore {
|
||||
windowToggleFps() {
|
||||
this.setFpsShown(!this.fpsShown);
|
||||
}
|
||||
|
||||
// stuff mainly for settings, really anything that uses local storage
|
||||
|
||||
@action
|
||||
setToken(token: string, save = false) {
|
||||
this.token = token;
|
||||
this.tokenLoaded = true;
|
||||
if (save) {
|
||||
secureLocalStorage.setItem("token", token);
|
||||
this.logger.info("Token saved to storage");
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
loadToken() {
|
||||
const token = secureLocalStorage.getItem("token") as string | null;
|
||||
|
||||
this.tokenLoaded = true;
|
||||
|
||||
if (token) {
|
||||
this.logger.debug("Loaded token from storage.");
|
||||
this.setToken(token);
|
||||
} else {
|
||||
this.logger.debug("No token found in storage.");
|
||||
this.setGatewayReady(true);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
logout() {
|
||||
this.token = null;
|
||||
this.tokenLoaded = false;
|
||||
this.isAppLoading = false;
|
||||
this.isGatewayReady = true;
|
||||
secureLocalStorage.clear();
|
||||
}
|
||||
|
||||
@action
|
||||
setFpsShown(value: boolean) {
|
||||
this.fpsShown = value;
|
||||
|
||||
secureLocalStorage.setItem("fpsShown", value);
|
||||
}
|
||||
|
||||
@action
|
||||
loadFpsShown() {
|
||||
this.fpsShown = (secureLocalStorage.getItem("fpsShown") as boolean | null) ?? false;
|
||||
}
|
||||
|
||||
@action
|
||||
setUpdaterEnabled(value: boolean) {
|
||||
this.updaterStore?.setEnabled(value);
|
||||
|
||||
secureLocalStorage.setItem("updaterEnabled", value);
|
||||
}
|
||||
|
||||
@action
|
||||
loadUpdaterEnabled() {
|
||||
this.updaterStore?.setEnabled((secureLocalStorage.getItem("updaterEnabled") as boolean | null) ?? true);
|
||||
}
|
||||
|
||||
@action
|
||||
loadSettings() {
|
||||
this.loadFpsShown();
|
||||
this.loadToken();
|
||||
this.loadUpdaterEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
export const appStore = new AppStore();
|
||||
|
182
src/stores/UpdaterStore.ts
Normal file
182
src/stores/UpdaterStore.ts
Normal file
@ -0,0 +1,182 @@
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { invoke } from "@tauri-apps/api/primitives";
|
||||
import { action, makeAutoObservable, observable } from "mobx";
|
||||
import useLogger from "../hooks/useLogger";
|
||||
import Logger from "../utils/Logger";
|
||||
import AppStore from "./AppStore";
|
||||
|
||||
export default class UpdaterStore {
|
||||
private readonly logger: Logger = useLogger("UpdaterStore");
|
||||
@observable initialized: boolean = false;
|
||||
@observable enabled: boolean = true;
|
||||
@observable checkingForUpdates: boolean = false;
|
||||
@observable updateDownloading: boolean = false;
|
||||
@observable updateDownloaded: boolean = false;
|
||||
@observable updateAvailable: boolean = false;
|
||||
@observable timer: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor(private readonly app: AppStore) {
|
||||
this.logger.info("Initializing UpdaterStore");
|
||||
makeAutoObservable(this);
|
||||
|
||||
const setupListeners = async () => {
|
||||
await listen("CHECKING_FOR_UPDATE", () => {
|
||||
this.logger.debug("Checking for updates");
|
||||
this.setCheckingForUpdates(true);
|
||||
this.setUpdateAvailable(false);
|
||||
this.setUpdateDownloading(false);
|
||||
this.setUpdateDownloaded(false);
|
||||
});
|
||||
|
||||
await listen("UPDATE_AVAILABLE", () => {
|
||||
this.logger.debug("Update available");
|
||||
this.setCheckingForUpdates(false);
|
||||
this.setUpdateAvailable(true);
|
||||
this.setUpdateDownloading(false);
|
||||
this.setUpdateDownloaded(false);
|
||||
});
|
||||
|
||||
await listen("UPDATE_NOT_AVAILABLE", () => {
|
||||
this.logger.debug("Update not available");
|
||||
this.setCheckingForUpdates(false);
|
||||
this.setUpdateAvailable(false);
|
||||
this.setUpdateDownloading(false);
|
||||
this.setUpdateDownloaded(false);
|
||||
});
|
||||
|
||||
await listen("UPDATE_DOWNLOADING", () => {
|
||||
this.logger.debug("Update downloading");
|
||||
this.setCheckingForUpdates(false);
|
||||
this.setUpdateAvailable(false);
|
||||
this.setUpdateDownloading(true);
|
||||
this.setUpdateDownloaded(false);
|
||||
});
|
||||
|
||||
await listen("UPDATE_DOWNLOADED", () => {
|
||||
this.logger.debug("Update downloaded");
|
||||
this.setCheckingForUpdates(false);
|
||||
this.setUpdateAvailable(false);
|
||||
this.setUpdateDownloading(false);
|
||||
this.setUpdateDownloaded(true);
|
||||
});
|
||||
};
|
||||
|
||||
setupListeners();
|
||||
|
||||
// @ts-expect-error - expose updater to window, don't use use this though
|
||||
window.updater = {
|
||||
setUpdateAvailable: this.setUpdateAvailable.bind(this),
|
||||
setUpdateDownloading: this.setUpdateDownloading.bind(this),
|
||||
setUpdateDownloaded: this.setUpdateDownloaded.bind(this),
|
||||
setCheckingForUpdates: this.setCheckingForUpdates.bind(this),
|
||||
checkForUpdates: this.checkForUpdates.bind(this),
|
||||
downloadUpdate: this.downloadUpdate.bind(this),
|
||||
quitAndInstall: this.quitAndInstall.bind(this),
|
||||
clearUpdateCache: this.clearCache.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
setCheckingForUpdates(value: boolean) {
|
||||
this.checkingForUpdates = value;
|
||||
}
|
||||
|
||||
@action
|
||||
setUpdateAvailable(value: boolean) {
|
||||
this.updateAvailable = value;
|
||||
}
|
||||
|
||||
@action
|
||||
setUpdateDownloading(value: boolean) {
|
||||
this.updateDownloading = value;
|
||||
}
|
||||
|
||||
@action
|
||||
setUpdateDownloaded(value: boolean) {
|
||||
this.updateDownloaded = value;
|
||||
}
|
||||
|
||||
async checkForUpdates() {
|
||||
if (this.checkingForUpdates) {
|
||||
this.logger.warn("Already checking for updates, skipping check");
|
||||
return;
|
||||
}
|
||||
|
||||
// if (this.app.settings.ignoreUpdates) {
|
||||
// this.logger.warn("Ignoring updates, skipping check");
|
||||
// return;
|
||||
// }
|
||||
this.logger.debug("Invoking update check");
|
||||
await invoke("check_for_updates", { ignorePrereleases: false });
|
||||
}
|
||||
|
||||
async downloadUpdate() {
|
||||
if (this.updateDownloading) {
|
||||
this.logger.warn("Already downloading an update");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.updateDownloaded) {
|
||||
this.logger.warn("An update is already pending installation");
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug("Invoking update download");
|
||||
await invoke("download_update");
|
||||
}
|
||||
|
||||
async quitAndInstall() {
|
||||
if (!this.updateAvailable) {
|
||||
this.logger.warn("No update is pending installation");
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug("Invoking update install");
|
||||
await invoke("install_update");
|
||||
}
|
||||
|
||||
@action
|
||||
setEnabled(value: boolean) {
|
||||
this.enabled = value;
|
||||
|
||||
if (value) {
|
||||
this.enable();
|
||||
} else {
|
||||
this.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
async enable() {
|
||||
this.logger.debug("Enabling updater");
|
||||
|
||||
if (this.initialized) {
|
||||
this.logger.debug("Updater already initialized, skipping init");
|
||||
return;
|
||||
}
|
||||
|
||||
// initial update check
|
||||
await this.checkForUpdates();
|
||||
|
||||
// start an update timer
|
||||
this.timer = setInterval(async () => {
|
||||
this.logger.debug("[UpdateTimer] Checking for updates");
|
||||
await this.checkForUpdates();
|
||||
}, 36e5); // 1 hour
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
@action
|
||||
disable() {
|
||||
this.logger.debug("Disabling updater");
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
}
|
||||
|
||||
async clearCache() {
|
||||
this.logger.debug("Clearing update cache");
|
||||
await invoke("clear_update_cache");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user