auto-updates
This commit is contained in:
parent
ac7a1025ac
commit
c86aed047c
10 changed files with 203 additions and 114 deletions
3
.github/workflows/oneclient_release.yml
vendored
3
.github/workflows/oneclient_release.yml
vendored
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
- name: Setup Packages
|
||||
uses: ./.github/actions/setup-packages
|
||||
|
||||
- uses: tauri-apps/tauri-action@v0
|
||||
- uses: tauri-apps/tauri-action@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
|
@ -50,3 +50,4 @@ jobs:
|
|||
tauriScript: pnpm -w oneclient:desktop
|
||||
args: ${{ matrix.args }}
|
||||
projectPath: ./apps/oneclient/desktop/
|
||||
uploadUpdaterJson: true
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use onelauncher_core::api::packages::modpack::data::ModpackArchive;
|
|||
use onelauncher_core::entity::clusters;
|
||||
use onelauncher_core::error::LauncherResult;
|
||||
|
||||
use crate::ext::updater::Update;
|
||||
use crate::oneclient::bundles::BundlesManager;
|
||||
use crate::oneclient::clusters::{OnlineClusterManifest, get_data_storage_versions};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
#[taurpc::procedures(path = "oneclient", export_to = "../frontend/src/bindings.gen.ts")]
|
||||
pub trait OneClientApi {
|
||||
|
|
@ -18,6 +20,14 @@ pub trait OneClientApi {
|
|||
|
||||
#[taurpc(alias = "getVersions")]
|
||||
async fn get_versions() -> LauncherResult<OnlineClusterManifest>;
|
||||
|
||||
#[taurpc(alias = "checkForUpdate")]
|
||||
async fn check_for_update<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
) -> LauncherResult<Option<Update>>;
|
||||
|
||||
#[taurpc(alias = "installUpdate")]
|
||||
async fn install_update<R: Runtime>(app_handle: AppHandle<R>) -> LauncherResult<()>;
|
||||
}
|
||||
|
||||
#[taurpc::ipc_type]
|
||||
|
|
@ -71,4 +81,19 @@ impl OneClientApi for OneClientApiImpl {
|
|||
async fn get_versions(self) -> LauncherResult<OnlineClusterManifest> {
|
||||
get_data_storage_versions().await
|
||||
}
|
||||
|
||||
async fn check_for_update<R: Runtime>(
|
||||
self,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> LauncherResult<Option<Update>> {
|
||||
crate::ext::updater::check_for_update(app_handle)
|
||||
.await
|
||||
.map_err(|e| onelauncher_core::error::LauncherError::from(anyhow::anyhow!(e)))
|
||||
}
|
||||
|
||||
async fn install_update<R: Runtime>(self, app_handle: AppHandle<R>) -> LauncherResult<()> {
|
||||
crate::ext::updater::install_update(app_handle)
|
||||
.await
|
||||
.map_err(|e| onelauncher_core::error::LauncherError::from(anyhow::anyhow!(e)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
pub mod specta;
|
||||
// pub mod updater;
|
||||
pub mod updater;
|
||||
|
|
|
|||
|
|
@ -1,117 +1,115 @@
|
|||
// use onelauncher_core::store::Core;
|
||||
// use tauri::plugin::TauriPlugin;
|
||||
// use tauri::{Emitter, Runtime};
|
||||
// use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
|
||||
// use tokio::sync::Mutex;
|
||||
use onelauncher_core::store::Core;
|
||||
use tauri::{Emitter, Manager, Runtime};
|
||||
use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// #[derive(Debug, Clone, specta::Type, serde::Serialize)]
|
||||
// pub struct Update {
|
||||
// pub version: String,
|
||||
// }
|
||||
#[derive(Debug, Clone, serde::Serialize, specta::Type)]
|
||||
pub struct Update {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
// impl Update {
|
||||
// fn new(update: &TauriPluginUpdate) -> Self {
|
||||
// Self {
|
||||
// version: update.version.clone(),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
impl Update {
|
||||
fn new(update: &TauriPluginUpdate) -> Self {
|
||||
Self {
|
||||
version: update.version.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Default)]
|
||||
// pub struct State {
|
||||
// install_lock: Mutex<()>,
|
||||
// }
|
||||
#[derive(Default)]
|
||||
pub struct UpdaterState {
|
||||
install_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
// async fn get_update(app: tauri::AppHandle) -> Result<Option<TauriPluginUpdate>, String> {
|
||||
// app.updater_builder()
|
||||
// .header("X-OneClient-Version", "stable")
|
||||
// .map_err(|e| e.to_string())?
|
||||
// .build()
|
||||
// .map_err(|e| e.to_string())?
|
||||
// .check()
|
||||
// .await
|
||||
// .map_err(|e| e.to_string())
|
||||
// }
|
||||
async fn get_update<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> Result<Option<TauriPluginUpdate>, String> {
|
||||
app.updater()
|
||||
.map_err(|e| e.to_string())?
|
||||
.check()
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
// #[derive(Debug, Clone, serde::Serialize, specta::Type, tauri_specta::Event)]
|
||||
// #[serde(rename_all = "camelCase", tag = "status")]
|
||||
// pub enum UpdateEvent {
|
||||
// Loading,
|
||||
// Error(String),
|
||||
// UpdateAvailable { update: Update },
|
||||
// NoUpdateAvailable,
|
||||
// Installing,
|
||||
// }
|
||||
#[derive(Debug, Clone, serde::Serialize, specta::Type)]
|
||||
#[serde(rename_all = "camelCase", tag = "status")]
|
||||
pub enum UpdateEvent {
|
||||
Loading,
|
||||
Error { error: String },
|
||||
UpdateAvailable { update: Update },
|
||||
NoUpdateAvailable,
|
||||
Installing,
|
||||
}
|
||||
|
||||
// #[tauri::command]
|
||||
// #[specta::specta]
|
||||
// pub async fn check_for_update(app: tauri::AppHandle) -> Result<Option<Update>, String> {
|
||||
// app.emit("updater", UpdateEvent::Loading).ok();
|
||||
pub async fn check_for_update<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> Result<Option<Update>, String> {
|
||||
app.emit("updater", UpdateEvent::Loading).ok();
|
||||
|
||||
// let update = match get_update(app.clone()).await {
|
||||
// Ok(update) => update,
|
||||
// Err(e) => {
|
||||
// app.emit("updater", UpdateEvent::Error(e.clone())).ok();
|
||||
// return Err(e);
|
||||
// }
|
||||
// };
|
||||
let update = match get_update(app.clone()).await {
|
||||
Ok(update) => update,
|
||||
Err(e) => {
|
||||
app.emit("updater", UpdateEvent::Error { error: e.clone() })
|
||||
.ok();
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// let update = update.map(|u| Update::new(&u));
|
||||
let update = update.map(|u| Update::new(&u));
|
||||
|
||||
// app.emit(
|
||||
// "updater",
|
||||
// update.clone().map_or(UpdateEvent::NoUpdateAvailable, |u| {
|
||||
// UpdateEvent::UpdateAvailable { update: u }
|
||||
// }),
|
||||
// )
|
||||
// .ok();
|
||||
app.emit(
|
||||
"updater",
|
||||
update.clone().map_or(UpdateEvent::NoUpdateAvailable, |u| {
|
||||
UpdateEvent::UpdateAvailable { update: u }
|
||||
}),
|
||||
)
|
||||
.ok();
|
||||
|
||||
// Ok(update)
|
||||
// }
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
// #[tauri::command]
|
||||
// #[specta::specta]
|
||||
// pub async fn install_update(
|
||||
// app: tauri::AppHandle,
|
||||
// state: tauri::State<'_, State>,
|
||||
// ) -> Result<(), String> {
|
||||
// let Ok(lock) = state.install_lock.try_lock() else {
|
||||
// return Err("Update already installing".into());
|
||||
// };
|
||||
pub async fn install_update<R: Runtime>(app: tauri::AppHandle<R>) -> Result<(), String> {
|
||||
let state = app.state::<UpdaterState>();
|
||||
let Ok(lock) = state.install_lock.try_lock() else {
|
||||
return Err("Update already installing".into());
|
||||
};
|
||||
|
||||
// app.emit("updater", UpdateEvent::Installing).ok();
|
||||
app.emit("updater", UpdateEvent::Installing).ok();
|
||||
|
||||
// get_update(app.clone())
|
||||
// .await?
|
||||
// .ok_or_else(|| "No update required".to_string())?
|
||||
// .download_and_install(|_, _| {}, || {})
|
||||
// .await
|
||||
// .map_err(|e| e.to_string())?;
|
||||
get_update(app.clone())
|
||||
.await?
|
||||
.ok_or_else(|| "No update required".to_string())?
|
||||
.download_and_install(|_, _| {}, || {})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// drop(lock);
|
||||
drop(lock);
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
app.restart();
|
||||
|
||||
// #[must_use]
|
||||
// pub fn plugin<R: Runtime>() -> TauriPlugin<R> {
|
||||
// tauri::plugin::Builder::new("onelauncher-updater")
|
||||
// .on_page_load(|window, _| {
|
||||
// #[cfg(target_os = "linux")]
|
||||
// let updater_available = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[cfg(not(target_os = "linux"))]
|
||||
// let updater_available = true;
|
||||
pub fn init<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
let updater_available = false;
|
||||
|
||||
// if updater_available {
|
||||
// window
|
||||
// .eval("window.__LAUNCHER_UPDATER__ = true;")
|
||||
// .expect("Failed to inject updater JS");
|
||||
// }
|
||||
// })
|
||||
// .js_init_script(format!(
|
||||
// r#"window.__ONECLIENT_VERSION__ = "{}";"#,
|
||||
// Core::get().launcher_version,
|
||||
// ))
|
||||
// .build()
|
||||
// }
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let updater_available = true;
|
||||
|
||||
if updater_available {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
window
|
||||
.eval("window.__LAUNCHER_UPDATER__ = true;")
|
||||
.expect("Failed to inject updater JS");
|
||||
|
||||
window
|
||||
.eval(&format!(
|
||||
r#"window.__ONECLIENT_VERSION__ = "{}";"#,
|
||||
Core::get().launcher_version,
|
||||
))
|
||||
.expect("Failed to inject version JS");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
|||
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
// .plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
|
|
@ -77,6 +77,7 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
|||
.menu(tauri::menu::Menu::new)
|
||||
.invoke_handler(router.into_handler())
|
||||
.setup(move |app| {
|
||||
app.manage(ext::updater::UpdaterState::default());
|
||||
setup_window(app.handle()).expect("failed to setup main window");
|
||||
Ok(())
|
||||
});
|
||||
|
|
@ -120,6 +121,8 @@ fn setup_window(handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Err
|
|||
.get_webview_window("main")
|
||||
.ok_or_else(|| anyhow::anyhow!("no window called main was found"))?;
|
||||
|
||||
ext::updater::init(handle)?;
|
||||
|
||||
// tokio::task::spawn(async move {
|
||||
// // let state = State::get().await.expect("failed to get state");
|
||||
// // let settings = state.settings.read().await;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"createUpdaterArtifacts": false,
|
||||
"createUpdaterArtifacts": true,
|
||||
"targets": [
|
||||
"app",
|
||||
"dmg",
|
||||
|
|
@ -112,8 +112,8 @@
|
|||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "",
|
||||
"endpoints": ["https://polyfrost.org/releases/oneclient/{{current_version}}/{{target}}/{{arch}}"]
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFGODk3MkMyMjg0MjFDMDUKUldRRkhFSW93bktKSHpkWjNEMXNzaDVINVpCTU8xSnhuK2RnV0dTZ2FkcFJWbG1zUkhGYTNjaUkK",
|
||||
"endpoints": ["https://github.com/Polyfrost/OneLauncher/releases/latest/download/latest.json"]
|
||||
},
|
||||
"deep-link": {
|
||||
"mobile": [],
|
||||
|
|
|
|||
|
|
@ -242,6 +242,8 @@ export type SkinVariant = "classic" | "slim"
|
|||
|
||||
export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated"
|
||||
|
||||
export type Update = { version: string }
|
||||
|
||||
/**
|
||||
* A game version of Minecraft
|
||||
*/
|
||||
|
|
@ -301,10 +303,15 @@ export type VersionType =
|
|||
*/
|
||||
"old_beta"
|
||||
|
||||
const ARGS_MAP = { 'core':'{"getLogByName":["id","name"],"killProcess":["pid"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getClusterById":["id"],"getRunningProcesses":[],"createSettingsProfile":["name"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"createCluster":["options"],"getUsers":[],"getUser":["uuid"],"fetchMinecraftProfile":["uuid"],"fetchLoggedInProfile":["access_token"],"getLinkedPackages":["cluster_id"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getGameVersions":[],"updateClusterProfile":["name","profile"],"changeSkin":["access_token","skin_url","skin_variant"],"convertUsernameUUID":["username_uuid"],"setDefaultUser":["uuid"],"getRunningProcessesByClusterId":["cluster_id"],"getPackageBody":["provider","body"],"getPackage":["provider","slug"],"removeCape":["access_token"],"removeCluster":["id"],"getClusters":[],"updateClusterById":["id","request"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"removePackage":["cluster_id","package_hash"],"searchPackages":["provider","query"],"readSettings":[],"getProfileOrDefault":["name"],"getScreenshots":["id"],"getWorlds":["id"],"getLoadersForVersion":["mc_version"],"getLogs":["id"],"getDefaultUser":["fallback"],"openMsaLogin":[],"installModpack":["modpack","cluster_id"],"setDiscordRPCMessage":["message"],"open":["input"],"getUsersFromAuthor":["provider","author"],"launchCluster":["id","uuid","search_for_java"],"getGlobalProfile":[],"removeUser":["uuid"],"getMultiplePackages":["provider","slugs"],"changeCape":["access_token","cape_uuid"],"isClusterRunning":["cluster_id"],"writeSettings":["setting"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'debug':'{"getType":[],"getLocale":[],"openDevTools":[],"getFamily":[],"getPackageVersion":[],"getBuildTimestamp":[],"getGitCommitHash":[],"getArch":[],"isInDev":[],"getOsVersion":[],"getPlatform":[]}', 'events':'{"message":["event"],"process":["event"],"ingress":["event"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"getVersions":[],"getBundlesFor":["cluster_id"]}' }
|
||||
export type Router = { 'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
|
||||
const ARGS_MAP = { 'events':'{"process":["event"],"message":["event"],"ingress":["event"]}', 'debug':'{"getPackageVersion":[],"getPlatform":[],"isInDev":[],"getArch":[],"getFamily":[],"getType":[],"getLocale":[],"openDevTools":[],"getBuildTimestamp":[],"getGitCommitHash":[],"getOsVersion":[]}', 'core':'{"isClusterRunning":["cluster_id"],"getGlobalProfile":[],"getDefaultUser":["fallback"],"openMsaLogin":[],"removeCape":["access_token"],"getRunningProcessesByClusterId":["cluster_id"],"createSettingsProfile":["name"],"getScreenshots":["id"],"getUser":["uuid"],"changeSkin":["access_token","skin_url","skin_variant"],"convertUsernameUUID":["username_uuid"],"fetchLoggedInProfile":["access_token"],"createCluster":["options"],"killProcess":["pid"],"removeUser":["uuid"],"removeCluster":["id"],"updateClusterById":["id","request"],"launchCluster":["id","uuid","search_for_java"],"setDefaultUser":["uuid"],"getWorlds":["id"],"getMultiplePackages":["provider","slugs"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getLogByName":["id","name"],"getPackageBody":["provider","body"],"setDiscordRPCMessage":["message"],"getGameVersions":[],"open":["input"],"readSettings":[],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUsersFromAuthor":["provider","author"],"getLogs":["id"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"installModpack":["modpack","cluster_id"],"changeCape":["access_token","cape_uuid"],"getPackage":["provider","slug"],"fetchMinecraftProfile":["uuid"],"getLoadersForVersion":["mc_version"],"writeSettings":["setting"],"getRunningProcesses":[],"getClusters":[],"getProfileOrDefault":["name"],"getClusterById":["id"],"updateClusterProfile":["name","profile"],"getLinkedPackages":["cluster_id"],"getUsers":[],"searchPackages":["provider","query"],"removePackage":["cluster_id","package_hash"]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'oneclient':'{"getBundlesFor":["cluster_id"],"getVersions":[],"checkForUpdate":[],"getClustersGroupedByMajor":[],"installUpdate":[]}' }
|
||||
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
|
||||
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]>,
|
||||
getVersions: () => Promise<OnlineClusterManifest> },
|
||||
getVersions: () => Promise<OnlineClusterManifest>,
|
||||
checkForUpdate: () => Promise<Update | null>,
|
||||
installUpdate: () => Promise<null> },
|
||||
'folders': { fromCluster: (folderName: string) => Promise<string>,
|
||||
openCluster: (folderName: string) => Promise<null> },
|
||||
'debug': { openDevTools: () => Promise<void>,
|
||||
|
|
@ -318,9 +325,6 @@ getOsVersion: () => Promise<string>,
|
|||
getGitCommitHash: () => Promise<string>,
|
||||
getBuildTimestamp: () => Promise<string>,
|
||||
getPackageVersion: () => Promise<string> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'core': { getClusters: () => Promise<ClusterModel[]>,
|
||||
getClusterById: (id: number) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: number) => Promise<null>,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
|
|||
import { Toasts } from '@/components';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import { bindings } from '@/main';
|
||||
import { checkForUpdate, installUpdate, listenForUpdateEvents } from '@/utils/updater';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { TanStackDevtools } from '@tanstack/react-devtools';
|
||||
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools';
|
||||
|
|
@ -74,10 +75,37 @@ function useDiscordRPC() {
|
|||
}, [location.pathname, location.search.clusterId, cluster?.name, managedPackage?.name]);
|
||||
}
|
||||
|
||||
function useAutoUpdate() {
|
||||
useEffect(() => {
|
||||
const unlistenPromise = listenForUpdateEvents(async (event) => {
|
||||
console.log('Updater event:', event);
|
||||
if (event.status === 'updateAvailable') {
|
||||
console.log('Update available, installing...');
|
||||
try {
|
||||
await installUpdate();
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to install update:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
checkForUpdate().then((update) => {
|
||||
if (update)
|
||||
console.log('Update found on initial check:', update.version);
|
||||
}).catch(e => console.error('Failed to check for update:', e));
|
||||
|
||||
return () => {
|
||||
unlistenPromise.then(unlisten => unlisten());
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
|
||||
function RootRoute() {
|
||||
const router = useRouter();
|
||||
const { setting } = useSettings();
|
||||
useDiscordRPC();
|
||||
useAutoUpdate();
|
||||
return (
|
||||
<RouterProvider
|
||||
navigate={(to, options) => router.navigate({ to, ...options })}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { JSX } from 'react';
|
||||
import { SheetPage } from '@/components';
|
||||
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router';
|
||||
import { CodeSnippet02Icon, MessageTextSquare01Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon } from '@untitled-theme/icons-react';
|
||||
import { CodeSnippet02Icon, Rocket02Icon, Sliders04Icon } from '@untitled-theme/icons-react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/app/settings')({
|
||||
|
|
@ -43,8 +43,8 @@ function RouteComponent() {
|
|||
[<Sliders04Icon key="mcsettings" />, 'Minecraft settings', '/minecraft'],
|
||||
],
|
||||
'About': [
|
||||
//[<RefreshCcw02Icon key="changelog" />, 'Changelog', '/'],
|
||||
//[<MessageTextSquare01Icon key="feedback" />, 'Feedback', '/'],
|
||||
// [<RefreshCcw02Icon key="changelog" />, 'Changelog', '/'],
|
||||
// [<MessageTextSquare01Icon key="feedback" />, 'Feedback', '/'],
|
||||
[<CodeSnippet02Icon key="dev" />, 'Developer Options', '/developer'],
|
||||
],
|
||||
}}
|
||||
|
|
|
|||
30
apps/oneclient/frontend/src/utils/updater.ts
Normal file
30
apps/oneclient/frontend/src/utils/updater.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import type { UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { listen } from '@tauri-apps/api/event';
|
||||
import { bindings } from '../main';
|
||||
|
||||
export interface Update {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type UpdateEvent =
|
||||
| { status: 'loading' }
|
||||
| { status: 'error'; error: string }
|
||||
| { status: 'updateAvailable'; update: Update }
|
||||
| { status: 'noUpdateAvailable' }
|
||||
| { status: 'installing' };
|
||||
|
||||
export async function checkForUpdate(): Promise<Update | null> {
|
||||
return await bindings.oneclient.checkForUpdate();
|
||||
}
|
||||
|
||||
export async function installUpdate(): Promise<void> {
|
||||
await bindings.oneclient.installUpdate();
|
||||
}
|
||||
|
||||
export async function listenForUpdateEvents(
|
||||
callback: (event: UpdateEvent) => void,
|
||||
): Promise<UnlistenFn> {
|
||||
return await listen<UpdateEvent>('updater', (event) => {
|
||||
callback(event.payload);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue