chore: get oneclient desktop working
2
.vscode/settings.json
vendored
|
|
@ -23,7 +23,7 @@
|
|||
"**/.crates": true,
|
||||
"**/routeTree.gen.ts": true
|
||||
},
|
||||
"rust-analyzer.procMacro.enable": false,
|
||||
"rust-analyzer.procMacro.enable": true,
|
||||
"rust-analyzer.procMacro.attributes.enable": false,
|
||||
"rust-analyzer.procMacro.ignored": {
|
||||
"async-trait": ["async_trait"],
|
||||
|
|
|
|||
29
Cargo.lock
generated
|
|
@ -3344,6 +3344,35 @@ version = "1.21.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "oneclient_gui"
|
||||
version = "0.2.0-alpha.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"interpulse",
|
||||
"onelauncher_core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"specta-typescript",
|
||||
"strum 0.27.1",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-deep-link",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-single-instance",
|
||||
"tauri-plugin-updater",
|
||||
"tauri-plugin-window-state",
|
||||
"tauri-specta",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"uuid 1.16.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onelauncher_core"
|
||||
version = "0.2.0-alpha.2"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"apps/oneclient/desktop",
|
||||
"apps/onelauncher/desktop",
|
||||
"packages/core",
|
||||
"packages/entity",
|
||||
|
|
|
|||
3
apps/oneclient/desktop/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Generated by Tauri
|
||||
.cargo/
|
||||
/gen
|
||||
18
apps/oneclient/desktop/App.entitlements
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- allows us to load minecraft libraries -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<!-- allows us to set DYLD_FALLBACK_LIBRARY_PATH set by cargo which breaks minecraft -->
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<!-- allows us to request microphone permissions (for things like voice chat) -->
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<!-- allows us to request camera permissions (for things like voice chat) -->
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
80
apps/oneclient/desktop/Cargo.toml
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
[package]
|
||||
name = "oneclient_gui"
|
||||
default-run = "oneclient_gui"
|
||||
description = "Next-generation open source Minecraft launcher"
|
||||
version = { workspace = true }
|
||||
license = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
documentation = { workspace = true }
|
||||
readme = { workspace = true }
|
||||
homepage = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
[lib]
|
||||
name = "oneclient_gui"
|
||||
crate-type = [
|
||||
"staticlib",
|
||||
"cdylib",
|
||||
"lib"
|
||||
]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
# core
|
||||
onelauncher_core = { workspace = true, features = [ "tauri" ] }
|
||||
interpulse = { workspace = true }
|
||||
|
||||
# tauri
|
||||
tauri-plugin-window-state = { workspace = true }
|
||||
tauri-plugin-single-instance = { workspace = true }
|
||||
tauri-plugin-updater = { workspace = true }
|
||||
tauri-plugin-clipboard-manager = { workspace = true }
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
tauri-plugin-deep-link = { workspace = true }
|
||||
|
||||
# code gen
|
||||
tauri-specta = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
specta-typescript = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
|
||||
# logging
|
||||
tracing = { workspace = true }
|
||||
tracing-error = { workspace = true }
|
||||
|
||||
# serialization & text
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
|
||||
# io
|
||||
tokio = { workspace = true }
|
||||
|
||||
# utils
|
||||
thiserror = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
|
||||
[dependencies.tauri]
|
||||
workspace = true
|
||||
features = [
|
||||
"macos-private-api",
|
||||
"image-ico",
|
||||
"image-png",
|
||||
"protocol-asset",
|
||||
"specta"
|
||||
]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"custom-protocol",
|
||||
"devtools"
|
||||
]
|
||||
devtools = [ "tauri/devtools" ]
|
||||
custom-protocol = [ "tauri/custom-protocol" ]
|
||||
24
apps/oneclient/desktop/Info.plist
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- registers our uri handler on macos -->
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>OneClient</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>oneclient</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<!-- allows us to request camera permissions (for things like voice chat) -->
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>A Minecraft mod is requesting access to your camera.</string>
|
||||
<!-- allows us to request microphone permissions (for things like voice chat) -->
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>A Minecraft mod is requesting access to your microphone.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
3
apps/oneclient/desktop/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build();
|
||||
}
|
||||
36
apps/oneclient/desktop/capabilities/default.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "capabilities for the main window",
|
||||
"windows": ["*"],
|
||||
"permissions": [
|
||||
"core:app:default",
|
||||
"core:webview:default",
|
||||
"core:image:default",
|
||||
"core:menu:default",
|
||||
"core:path:default",
|
||||
"core:resources:default",
|
||||
"core:tray:default",
|
||||
"core:window:default",
|
||||
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-create",
|
||||
"core:window:allow-destroy",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:window:allow-internal-toggle-maximize",
|
||||
"core:window:allow-set-decorations",
|
||||
"core:window:allow-set-title-bar-style",
|
||||
"core:webview:allow-internal-toggle-devtools",
|
||||
|
||||
"core:event:default",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-confirm",
|
||||
"deep-link:default",
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
27
apps/oneclient/desktop/package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "@oneclient/desktop",
|
||||
"type": "module",
|
||||
"version": "0.1.0-alpha.2",
|
||||
"private": true,
|
||||
"description": "Next-generation open source Minecraft launcher",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:polyfrost/onelauncher",
|
||||
"directory": "apps/desktop"
|
||||
},
|
||||
"bugs": "https://github.com/polyfrost/onelauncher/issues",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
"dev": "tauri dev",
|
||||
"tauri": "tauri",
|
||||
"tauri-ext": "pnpm --filter @onelauncher/scripts -- tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@oneclient/frontend": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.4"
|
||||
}
|
||||
}
|
||||
30
apps/oneclient/desktop/src/api/commands.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
pub mod cluster;
|
||||
pub mod users;
|
||||
pub mod processor;
|
||||
pub mod package;
|
||||
pub mod other;
|
||||
pub mod settings;
|
||||
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! collect_commands {
|
||||
() => {{
|
||||
use $crate::api::commands::*;
|
||||
tauri_specta::collect_commands![
|
||||
users::get_users,
|
||||
users::get_user,
|
||||
users::remove_user,
|
||||
users::get_default_user,
|
||||
users::set_default_user,
|
||||
users::begin_ms_flow,
|
||||
|
||||
cluster::create_cluster,
|
||||
cluster::get_clusters,
|
||||
cluster::get_cluster_by_id,
|
||||
|
||||
settings::get_global_profile,
|
||||
settings::get_profile_or_default,
|
||||
|
||||
other::open_dev_tools,
|
||||
]
|
||||
}};
|
||||
}
|
||||
40
apps/oneclient/desktop/src/api/commands/cluster.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use onelauncher_core::{api::{cluster, setting_profiles}, entity::{clusters::Model, icon::Icon, loader::GameLoader}, migration::sea_orm};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
use crate::api::error::SerializableResult;
|
||||
|
||||
#[derive(Serialize, Deserialize, Type)]
|
||||
pub struct CreateCluster {
|
||||
name: String,
|
||||
mc_version: String,
|
||||
mc_loader: GameLoader,
|
||||
mc_loader_version: Option<String>,
|
||||
icon_url: Option<Icon>
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn create_cluster(options: CreateCluster) -> SerializableResult<Model> {
|
||||
let thing = cluster::create_cluster(&options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon_url).await?;
|
||||
|
||||
if setting_profiles::dao::get_profile_by_name(&options.name).await?.is_none() {
|
||||
setting_profiles::create_profile(&options.name, async |mut profile| {
|
||||
profile.mem_max = sea_orm::ActiveValue::Set(Some(2048));
|
||||
Ok(profile)
|
||||
}).await?;
|
||||
}
|
||||
|
||||
Ok(thing)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_clusters() -> SerializableResult<Vec<Model>> {
|
||||
Ok(cluster::dao::get_all_clusters().await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_cluster_by_id(id: i64) -> SerializableResult<Option<Model>> {
|
||||
Ok(cluster::dao::get_cluster_by_id(id).await?)
|
||||
}
|
||||
6
apps/oneclient/desktop/src/api/commands/other.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#[onelauncher_core::command]
|
||||
#[allow(unused_parens)]
|
||||
pub fn open_dev_tools(webview: tauri::WebviewWindow) {
|
||||
#[cfg(feature = "devtools")]
|
||||
webview.open_devtools();
|
||||
}
|
||||
234
apps/oneclient/desktop/src/api/commands/package.rs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// use std::path::PathBuf;
|
||||
|
||||
// use onelauncher::cluster::content::package;
|
||||
// use onelauncher::data::{Loader, ManagedPackage, ManagedUser, ManagedVersion, PackageType};
|
||||
// use onelauncher::package::content::Providers;
|
||||
// use onelauncher::package::import::ImportType;
|
||||
// use onelauncher::store::{
|
||||
// Author, ClusterPath, Package, PackageBody, PackagePath, ProviderSearchResults,
|
||||
// };
|
||||
// use onelauncher::utils::pagination::Pagination;
|
||||
// use uuid::Uuid;
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_launcher_instances(
|
||||
// launcher: ImportType,
|
||||
// path: Option<PathBuf>,
|
||||
// ) -> Result<(String, Vec<String>), String> {
|
||||
// let (dir, instances) = onelauncher::api::package::import::get_launcher_instances(launcher, path).await?;
|
||||
// Ok((
|
||||
// dir.to_str().ok_or("could not convert pathbuf to string")?.to_string(),
|
||||
// instances
|
||||
// ))
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn import_instances(
|
||||
// launcher: ImportType,
|
||||
// base_path: PathBuf,
|
||||
// instances: Vec<String>,
|
||||
// ) -> Result<(), String> {
|
||||
// onelauncher::api::package::import::import_instances(launcher, base_path, instances).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_provider_package(
|
||||
// provider: Providers,
|
||||
// project_id: String,
|
||||
// ) -> Result<ManagedPackage, String> {
|
||||
// Ok(provider.get(&project_id).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_provider_packages(
|
||||
// provider: Providers,
|
||||
// project_ids: Vec<String>,
|
||||
// ) -> Result<Vec<ManagedPackage>, String> {
|
||||
// Ok(provider.get_multiple(&project_ids).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_all_provider_package_versions(
|
||||
// provider: Providers,
|
||||
// project_id: String,
|
||||
// game_versions: Option<Vec<String>>,
|
||||
// loaders: Option<Vec<Loader>>,
|
||||
// page: Option<u32>,
|
||||
// page_size: Option<u16>,
|
||||
// ) -> Result<(Vec<ManagedVersion>, Pagination), String> {
|
||||
// Ok(provider
|
||||
// .get_all_versions(&project_id, game_versions, loaders, page, page_size)
|
||||
// .await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_provider_package_versions(
|
||||
// provider: Providers,
|
||||
// versions: Vec<String>,
|
||||
// ) -> Result<Vec<ManagedVersion>, String> {
|
||||
// Ok(provider.get_versions(versions).await?)
|
||||
// }
|
||||
|
||||
// #[derive(specta::Type, serde::Deserialize, serde::Serialize)]
|
||||
// pub struct ProviderSearchQuery {
|
||||
// query: Option<String>,
|
||||
// limit: Option<u8>,
|
||||
// offset: Option<u32>,
|
||||
// game_versions: Option<Vec<String>>,
|
||||
// categories: Option<Vec<String>>,
|
||||
// loaders: Option<Vec<Loader>>,
|
||||
// package_types: Option<Vec<PackageType>>,
|
||||
// open_source: Option<bool>,
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn search_provider_packages(
|
||||
// provider: Providers,
|
||||
// query: ProviderSearchQuery,
|
||||
// ) -> Result<ProviderSearchResults, String> {
|
||||
// Ok(provider
|
||||
// .search(
|
||||
// query.query,
|
||||
// query.limit,
|
||||
// query.offset,
|
||||
// query.game_versions,
|
||||
// query.categories,
|
||||
// query.loaders,
|
||||
// query.package_types,
|
||||
// query.open_source,
|
||||
// )
|
||||
// .await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_provider_authors(
|
||||
// provider: Providers,
|
||||
// author: Author,
|
||||
// ) -> Result<Vec<ManagedUser>, String> {
|
||||
// Ok(provider.get_authors(&author).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_package_body(provider: Providers, body: PackageBody) -> Result<String, String> {
|
||||
// Ok(provider.get_package_body(&body).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn download_provider_package(
|
||||
// provider: Providers,
|
||||
// package_id: String,
|
||||
// cluster_id: Uuid,
|
||||
// game_version: Option<String>,
|
||||
// loader: Option<Loader>,
|
||||
// package_version: Option<String>,
|
||||
// ) -> Result<(), String> {
|
||||
// let mut cluster = onelauncher::cluster::get_by_uuid(cluster_id)
|
||||
// .await?
|
||||
// .ok_or("cluster not found")?;
|
||||
|
||||
// let mgd_pkg = provider.get(&package_id).await?;
|
||||
|
||||
// let (pkg_path, pkg) = package::download_package(
|
||||
// &mgd_pkg,
|
||||
// &mut cluster,
|
||||
// game_version,
|
||||
// loader,
|
||||
// package_version,
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
// package::add_package(
|
||||
// &cluster.cluster_path(),
|
||||
// pkg_path,
|
||||
// pkg,
|
||||
// )
|
||||
// .await?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_cluster_package(
|
||||
// cluster_path: ClusterPath,
|
||||
// package_path: PackagePath,
|
||||
// package_type: PackageType,
|
||||
// ) -> Result<Package, String> {
|
||||
// Ok(package::get_package(&cluster_path, &package_path, package_type).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_cluster_packages(
|
||||
// cluster_path: ClusterPath,
|
||||
// package_type: PackageType,
|
||||
// ) -> Result<Vec<Package>, String> {
|
||||
// Ok(package::get_packages(&cluster_path, package_type).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn add_cluster_package(
|
||||
// cluster_path: ClusterPath,
|
||||
// file_name: String,
|
||||
// package_type: PackageType,
|
||||
// pkg: Package,
|
||||
// ) -> Result<(), String> {
|
||||
// let package_path = PackagePath::new(package_type, file_name)?;
|
||||
// package::add_package(&cluster_path, package_path, pkg).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn remove_cluster_package(
|
||||
// cluster_path: ClusterPath,
|
||||
// file_name: String,
|
||||
// package_type: PackageType,
|
||||
// ) -> Result<(), String> {
|
||||
// let package_path = PackagePath::new(package_type, file_name)?;
|
||||
// package::remove_package(&cluster_path, &package_path).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn set_cluster_package_enabled(
|
||||
// cluster_path: ClusterPath,
|
||||
// file_name: String,
|
||||
// package_type: PackageType,
|
||||
// enabled: bool,
|
||||
// ) -> Result<(), String> {
|
||||
// let package_path = PackagePath::new(package_type, file_name)?;
|
||||
// package::set_package_enabled(&cluster_path, &package_path, enabled).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn sync_cluster_packages(cluster_path: ClusterPath) -> Result<(), String> {
|
||||
// package::sync_packages(&cluster_path).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn sync_cluster_packages_by_type(
|
||||
// cluster_path: ClusterPath,
|
||||
// package_type: PackageType,
|
||||
// clear: Option<bool>,
|
||||
// ) -> Result<(), String> {
|
||||
// package::sync_packages_by_type(&cluster_path, package_type, clear).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
61
apps/oneclient/desktop/src/api/commands/processor.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// use chrono::{DateTime, Utc};
|
||||
// use onelauncher::processor::{self, DetailedProcess};
|
||||
// use onelauncher::store::{Cluster, ClusterPath};
|
||||
// use uuid::Uuid;
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_running_clusters() -> Result<Vec<Cluster>, String> {
|
||||
// Ok(processor::get_running_clusters().await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn is_cluster_running(uuid: Uuid) -> Result<bool, String> {
|
||||
// Ok(processor::is_cluster_running(uuid).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_processes_by_path(path: ClusterPath) -> Result<Vec<Uuid>, String> {
|
||||
// Ok(processor::get_uuids_by_cluster_path(path).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_processes_detailed_by_path(
|
||||
// path: ClusterPath,
|
||||
// ) -> Result<Vec<DetailedProcess>, String> {
|
||||
// Ok(processor::get_processes_detailed_by_path(path).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_process_detailed_by_id(uuid: Uuid) -> Result<DetailedProcess, String> {
|
||||
// Ok(processor::get_process_detailed_by_id(uuid).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_user_by_process(uuid: Uuid) -> Result<Option<Uuid>, String> {
|
||||
// Ok(processor::get_user_by_process(uuid).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_process_started_at(uuid: Uuid) -> Result<DateTime<Utc>, String> {
|
||||
// Ok(processor::get_process_started_at(uuid).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn get_pid_by_uuid(uuid: Uuid) -> Result<u32, String> {
|
||||
// Ok(processor::get_pid_by_uuid(uuid).await?)
|
||||
// }
|
||||
|
||||
// #[specta::specta]
|
||||
// #[tauri::command]
|
||||
// pub async fn kill_process(uuid: Uuid) -> Result<(), String> {
|
||||
// processor::kill_by_uuid(uuid).await?;
|
||||
// Ok(())
|
||||
// }
|
||||
18
apps/oneclient/desktop/src/api/commands/settings.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use onelauncher_core::entity::setting_profiles::Model as SettingsModel;
|
||||
use onelauncher_core::api::setting_profiles;
|
||||
|
||||
use crate::api::error::SerializableResult;
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_global_profile() -> SerializableResult<SettingsModel> {
|
||||
Ok(setting_profiles::get_global_profile().await)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_profile_or_default(
|
||||
name: Option<String>,
|
||||
) -> SerializableResult<SettingsModel> {
|
||||
Ok(setting_profiles::dao::get_profile_or_default(name.as_ref()).await?)
|
||||
}
|
||||
112
apps/oneclient/desktop/src/api/commands/users.rs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
use onelauncher_core::{api::credentials, error::LauncherResult, store::{credentials::{MinecraftCredentials, MinecraftLogin}, Core}};
|
||||
use tauri::{AppHandle, Manager};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::api::error::SerializableResult;
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_users() -> SerializableResult<Vec<MinecraftCredentials>> {
|
||||
Ok(credentials::users().await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_user(uuid: Uuid) -> SerializableResult<MinecraftCredentials> {
|
||||
Ok(credentials::get_user(uuid).await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn remove_user(uuid: Uuid) -> SerializableResult<()> {
|
||||
Ok(credentials::remove_user(uuid).await?)
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn get_default_user(
|
||||
fallback: Option<bool>,
|
||||
) -> SerializableResult<Option<MinecraftCredentials>> {
|
||||
let uuid = credentials::get_default_user().await?;
|
||||
|
||||
if fallback.is_some_and(|fallback| fallback) && uuid.is_none() {
|
||||
return Ok(credentials::users().await?.first().cloned());
|
||||
}
|
||||
|
||||
match uuid {
|
||||
Some(uuid) => Ok(Some(credentials::get_user(uuid).await?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn set_default_user(uuid: Option<Uuid>) -> SerializableResult<()> {
|
||||
credentials::set_default_user(uuid).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[specta::specta]
|
||||
#[tauri::command]
|
||||
pub async fn begin_ms_flow(handle: tauri::AppHandle) -> SerializableResult<Option<MinecraftCredentials>> {
|
||||
let flow = credentials::begin().await?;
|
||||
let result = spawn_webview(handle, flow).await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn spawn_webview(
|
||||
handle: AppHandle,
|
||||
flow: MinecraftLogin,
|
||||
) -> LauncherResult<Option<MinecraftCredentials>> {
|
||||
let now = chrono::Utc::now();
|
||||
|
||||
if let Some(win) = handle.get_webview_window("login") {
|
||||
win.close()?;
|
||||
}
|
||||
|
||||
let win = tauri::WebviewWindowBuilder::new(
|
||||
&handle,
|
||||
"login",
|
||||
tauri::WebviewUrl::External(
|
||||
flow.redirect_uri
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("failed to parse auth redirect url"))?,
|
||||
),
|
||||
)
|
||||
.title(format!("Login to {}", Core::get().launcher_name))
|
||||
.center()
|
||||
.focused(true)
|
||||
.build()?;
|
||||
|
||||
win.request_user_attention(Some(tauri::UserAttentionType::Critical))?;
|
||||
|
||||
while (chrono::Utc::now() - now) < chrono::Duration::minutes(10) {
|
||||
if win.title().is_err() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if win
|
||||
.url()?
|
||||
.as_str()
|
||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||
{
|
||||
if let Some((_, code)) = win
|
||||
.url()?
|
||||
.query_pairs()
|
||||
.find(|x| x.0 == "code")
|
||||
{
|
||||
win.close()?;
|
||||
let value = credentials::finish(&code.clone(), flow).await?;
|
||||
|
||||
return Ok(Some(value));
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
win.close()?;
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
61
apps/oneclient/desktop/src/api/error.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use onelauncher_core::error::LauncherError;
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
use specta::Type;
|
||||
|
||||
#[derive(thiserror::Error, Debug, Type)]
|
||||
#[serde(tag = "kind", content = "data")]
|
||||
pub enum SerializableError {
|
||||
#[error(transparent)]
|
||||
CoreError(#[serde(skip)] #[from] onelauncher_core::error::LauncherError),
|
||||
}
|
||||
|
||||
pub type SerializableResult<T> = std::result::Result<T, SerializableError>;
|
||||
|
||||
macro_rules! impl_serialize {
|
||||
($($variant:ident),* $(,)?) => {
|
||||
impl Serialize for SerializableError {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
SerializableError::CoreError(error) => {
|
||||
display_tracing_error(error);
|
||||
|
||||
let mut state = serializer.serialize_struct("CoreError", 2)?;
|
||||
state.serialize_field("field_name", "CoreError")?;
|
||||
state.serialize_field("message", &error.to_string())?;
|
||||
state.end()
|
||||
}
|
||||
$(
|
||||
SerializableError::$variant(message) => {
|
||||
let mut state = serializer.serialize_struct(stringify!($variant), 2)?;
|
||||
state.serialize_field("field_name", stringify!($variant))?;
|
||||
state.serialize_field("message", &message.to_string())?;
|
||||
state.end()
|
||||
},
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_serialize! {}
|
||||
|
||||
pub fn display_tracing_error(err: &LauncherError) {
|
||||
match get_span_trace(err) {
|
||||
Some(span_trace) => {
|
||||
tracing::error!(error = %err, span_trace = %span_trace);
|
||||
}
|
||||
None => {
|
||||
tracing::error!(error = %err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_span_trace<'a>(
|
||||
error: &'a (dyn std::error::Error + 'static),
|
||||
) -> Option<&'a tracing_error::SpanTrace> {
|
||||
error.source().and_then(tracing_error::ExtractSpanTrace::span_trace)
|
||||
}
|
||||
8
apps/oneclient/desktop/src/api/events.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#[macro_export]
|
||||
macro_rules! collect_events {
|
||||
() => {{
|
||||
tauri_specta::collect_events! {
|
||||
onelauncher_core::api::proxy::event::LauncherEvent
|
||||
}
|
||||
}};
|
||||
}
|
||||
4
apps/oneclient/desktop/src/api/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod commands;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
pub mod statics;
|
||||
26
apps/oneclient/desktop/src/api/statics.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use onelauncher_core::{constants::{NATIVE_ARCH, TARGET_OS}, store::Core};
|
||||
use serde::Serialize;
|
||||
use specta::Type;
|
||||
|
||||
#[derive(Serialize, Type)]
|
||||
pub struct ProgramInfo {
|
||||
launcher_version: String,
|
||||
webview_version: String,
|
||||
tauri_version: String,
|
||||
dev_build: bool,
|
||||
platform: String,
|
||||
arch: String,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_program_info() -> ProgramInfo {
|
||||
|
||||
ProgramInfo {
|
||||
launcher_version: Core::get().launcher_version.clone(),
|
||||
webview_version: tauri::webview_version().unwrap_or_else(|_| "UNKNOWN".into()),
|
||||
tauri_version: tauri::VERSION.into(),
|
||||
dev_build: tauri::is_dev(),
|
||||
platform: TARGET_OS.into(),
|
||||
arch: NATIVE_ARCH.into(),
|
||||
}
|
||||
}
|
||||
3
apps/oneclient/desktop/src/constants.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// === API ===
|
||||
pub const CURSEFORGE_API_KEY: &str = "$2a$10$6utA1UNSmFPrE/Lh7b7ndeeGmiOkjKNY8kpFB0fsmE/d42ZAfFgCe";
|
||||
pub const DISCORD_CLIENT_ID: &str = "1274084193193955408";
|
||||
0
apps/oneclient/desktop/src/error.rs
Normal file
2
apps/oneclient/desktop/src/ext/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod specta;
|
||||
pub mod updater;
|
||||
17
apps/oneclient/desktop/src/ext/specta.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn formatter(file: &Path) -> io::Result<()> {
|
||||
Command::new("pnpm")
|
||||
.arg("eslint")
|
||||
.arg("--flag")
|
||||
.arg("unstable_ts_config")
|
||||
.arg("--fix")
|
||||
.arg(file)
|
||||
.output()
|
||||
.map(|_| ())
|
||||
.map_err(io::Error::other)
|
||||
}
|
||||
|
||||
const _: specta_typescript::FormatterFn = formatter;
|
||||
117
apps/oneclient/desktop/src/ext/updater.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
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;
|
||||
|
||||
#[derive(Debug, Clone, specta::Type, serde::Serialize)]
|
||||
pub struct Update {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
impl Update {
|
||||
fn new(update: &TauriPluginUpdate) -> Self {
|
||||
Self {
|
||||
version: update.version.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
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())
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn check_for_update(app: tauri::AppHandle) -> 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 = update.map(|u| Update::new(&u));
|
||||
|
||||
app.emit(
|
||||
"updater",
|
||||
update.clone().map_or(UpdateEvent::NoUpdateAvailable, |u| {
|
||||
UpdateEvent::UpdateAvailable { update: u }
|
||||
}),
|
||||
)
|
||||
.ok();
|
||||
|
||||
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());
|
||||
};
|
||||
|
||||
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())?;
|
||||
|
||||
drop(lock);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let updater_available = true;
|
||||
|
||||
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()
|
||||
}
|
||||
120
apps/oneclient/desktop/src/lib.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
use onelauncher_core::api::proxy::ProxyTauri;
|
||||
use onelauncher_core::error::LauncherResult;
|
||||
use onelauncher_core::store::proxy::ProxyState;
|
||||
use onelauncher_core::store::semaphore::SemaphoreStore;
|
||||
use onelauncher_core::store::{Core, CoreOptions, Dirs, State};
|
||||
use tauri::{Emitter, Manager};
|
||||
|
||||
pub mod api;
|
||||
pub mod constants;
|
||||
pub mod ext;
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
pub struct SingleInstancePayload {
|
||||
args: Vec<String>,
|
||||
cwd: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
async fn initialize_core() -> LauncherResult<()> {
|
||||
let opts = CoreOptions {
|
||||
curseforge_api_key: Some(constants::CURSEFORGE_API_KEY.to_string()),
|
||||
launcher_name: "OneClient".to_string(),
|
||||
launcher_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
launcher_website: "https://polyfrost.org/".to_string(),
|
||||
discord_client_id: None, //Some(constants::DISCORD_CLIENT_ID.to_string()), // disabled for now
|
||||
fetch_attempts: 3,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Core::initialize(opts).await?;
|
||||
Dirs::get().await?;
|
||||
onelauncher_core::start_logger().await;
|
||||
SemaphoreStore::get().await;
|
||||
tracing::info!("initialized core modules");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult<tauri::App> {
|
||||
let prebuild = tauri_specta::Builder::<tauri::Wry>::new()
|
||||
.commands(collect_commands!())
|
||||
.events(collect_events!());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
prebuild
|
||||
.export(
|
||||
specta_typescript::Typescript::default()
|
||||
.bigint(specta_typescript::BigIntExportBehavior::BigInt)
|
||||
.formatter(ext::specta::formatter),
|
||||
"../frontend/src/bindings.gen.ts",
|
||||
)
|
||||
.expect("failed to export debug bindings!");
|
||||
|
||||
let builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(ext::updater::plugin())
|
||||
.manage(ext::updater::State::default())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
// .plugin(api::init())
|
||||
.menu(tauri::menu::Menu::new)
|
||||
.invoke_handler(prebuild.invoke_handler())
|
||||
.setup(move |app| {
|
||||
prebuild.mount_events(app.handle());
|
||||
setup_window(app.handle()).expect("failed to setup main window");
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let app = builder
|
||||
.build(tauri::generate_context!())
|
||||
.expect("failed to build tauri application");
|
||||
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
async fn initialize_state(handle: &tauri::AppHandle) -> LauncherResult<()> {
|
||||
let proxy = ProxyTauri::new(handle.clone());
|
||||
ProxyState::initialize(proxy).await?;
|
||||
|
||||
State::get().await?;
|
||||
|
||||
tracing::info!("initialized launcher successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run() {
|
||||
initialize_core().await.expect("failed to initialize core");
|
||||
let app = initialize_tauri(tauri::Builder::default())
|
||||
.await
|
||||
.expect("failed to initialize tauri");
|
||||
initialize_state(app.handle())
|
||||
.await
|
||||
.expect("failed to initialize state");
|
||||
|
||||
app.run(|_, _| {});
|
||||
}
|
||||
|
||||
fn setup_window(handle: &tauri::AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let win = handle
|
||||
.get_webview_window("main")
|
||||
.ok_or_else(|| anyhow::anyhow!("no window called main was found"))?;
|
||||
|
||||
// tokio::task::spawn(async move {
|
||||
// // let state = State::get().await.expect("failed to get state");
|
||||
// // let settings = state.settings.read().await;
|
||||
// // win.set_decorations(settings.);
|
||||
// });
|
||||
|
||||
win.show()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
7
apps/oneclient/desktop/src/main.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
oneclient_gui::run().await;
|
||||
}
|
||||
125
apps/oneclient/desktop/tauri.conf.json
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
|
||||
"productName": "OneClient",
|
||||
"identifier": "org.polyfrost.oneclient",
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"macOSPrivateApi": true,
|
||||
"security": {
|
||||
"csp": null,
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": {
|
||||
"allow": [
|
||||
"$DATA/OneClient/**",
|
||||
"$RESOURCE/**"
|
||||
]
|
||||
}
|
||||
},
|
||||
"freezePrototype": true,
|
||||
"capabilities": []
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "OneClient",
|
||||
"titleBarStyle": "Overlay",
|
||||
"hiddenTitle": true,
|
||||
"width": 1280,
|
||||
"height": 802,
|
||||
"minWidth": 1020,
|
||||
"minHeight": 600,
|
||||
"resizable": true,
|
||||
"center": true,
|
||||
"decorations": false,
|
||||
"fullscreen": false,
|
||||
"shadow": true,
|
||||
"visible": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm --filter @oneclient/frontend run dev",
|
||||
"beforeBuildCommand": "pnpm --filter @oneclient/frontend run build",
|
||||
"frontendDist": "../frontend/dist",
|
||||
"devUrl": "http://localhost:8001"
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": [
|
||||
"app",
|
||||
"dmg",
|
||||
"deb",
|
||||
"rpm",
|
||||
"appimage",
|
||||
"nsis"
|
||||
],
|
||||
"category": "Game",
|
||||
"icon": [
|
||||
"../distribution/icons/32x32.png",
|
||||
"../distribution/icons/128x128.png",
|
||||
"../distribution/icons/128x128@2x.png",
|
||||
"../distribution/icons/icon.icns",
|
||||
"../distribution/icons/icon.ico"
|
||||
],
|
||||
"copyright": "Polyfrost",
|
||||
"publisher": "Polyfrost",
|
||||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://polyfrost.org/",
|
||||
"longDescription": "Next-generation open source Minecraft launcher",
|
||||
"createUpdaterArtifacts": true,
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": [],
|
||||
"desktopTemplate": "../distribution/linux/org.polyfrost.oneclient.desktop.template"
|
||||
},
|
||||
"rpm": {
|
||||
"desktopTemplate": "../distribution/linux/org.polyfrost.oneclient.desktop.template"
|
||||
},
|
||||
"appimage": {
|
||||
|
||||
}
|
||||
},
|
||||
"shortDescription": "OneClient",
|
||||
"macOS": {
|
||||
"entitlements": "App.entitlements",
|
||||
"exceptionDomain": null,
|
||||
"frameworks": [],
|
||||
"dmg": {
|
||||
"appPosition": {
|
||||
"x": 190,
|
||||
"y": 190
|
||||
},
|
||||
"applicationFolderPosition": {
|
||||
"x": 470,
|
||||
"y": 190
|
||||
}
|
||||
},
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null,
|
||||
"minimumSystemVersion": "10.15"
|
||||
},
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"webviewInstallMode": {
|
||||
"silent": true,
|
||||
"type": "downloadBootstrapper"
|
||||
},
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "https://timestamp.digicert.com"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "",
|
||||
"endpoints": ["https://polyfrost.org/releases/oneclient/{{current_version}}/{{target}}/{{arch}}"]
|
||||
},
|
||||
"deep-link": {
|
||||
"mobile": [],
|
||||
"desktop": {
|
||||
"schemes": ["oneclient"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
apps/oneclient/distribution/aur/cli/PKGBUILD
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
pkgname=onelauncher-bin
|
||||
pkgver=0.1.0
|
||||
pkgrel=2
|
||||
pkgdesc='Next-generation open source Minecraft launcher'
|
||||
url='https://polyfrost.org/launcher'
|
||||
arch=('x86_64')
|
||||
license=('GPL3')
|
||||
|
||||
makedepends=('rust' 'pnpm')
|
||||
depends=(
|
||||
'openssl-1.1' 'dbus' 'freetype2' 'gtk3' 'libappindicator-gtk3' 'librsvg' 'libsoup' 'webkit2gtk'
|
||||
'libgl' 'libpulse' 'libx11' 'libxcursor' 'libxext' 'libxxf86vm'
|
||||
)
|
||||
optdepends=(
|
||||
'xorg-xrandr: very legacy minecraft versions'
|
||||
)
|
||||
provides=('onelauncher')
|
||||
conflicts=('onelauncher')
|
||||
source=("https://launcher.polyfrost.org/versions/${pkgver}/linux/onelauncher_${pkgver}_amd64.deb")
|
||||
sha256sums=('')
|
||||
|
||||
build() {
|
||||
cd "$srcdir/"
|
||||
tar xf data.tar.gz
|
||||
}
|
||||
|
||||
package() {
|
||||
cp -r "${srcdir}/usr/" "${pkgdir}"
|
||||
}
|
||||
50
apps/oneclient/distribution/aur/gui/PKGBUILD
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
pkgname=onelauncher
|
||||
pkgver=0.1.0
|
||||
pkgrel=2
|
||||
pkgdesc='Next-generation open source Minecraft launcher'
|
||||
url='https://polyfrost.org/launcher'
|
||||
arch=('x86_64')
|
||||
license=('GPL3')
|
||||
|
||||
makedepends=('rust' 'pnpm')
|
||||
depends=(
|
||||
'openssl-1.1' 'dbus' 'freetype2' 'gtk3' 'libappindicator-gtk3' 'librsvg' 'libsoup' 'webkit2gtk'
|
||||
'libgl' 'libpulse' 'libx11' 'libxcursor' 'libxext' 'libxxf86vm'
|
||||
)
|
||||
optdepends=(
|
||||
'xorg-xrandr: very legacy minecraft versions'
|
||||
)
|
||||
conflicts=('onelauncher-bin')
|
||||
source=(
|
||||
"$pkgname-$pkgver.tar.gz:https://github.com/polyfrost/onelauncher/archive/refs/tags/v${pkgver}.tar.gz"
|
||||
"onelauncher.desktop"
|
||||
)
|
||||
sha256sums=('')
|
||||
options=('!lto')
|
||||
|
||||
prepare() {
|
||||
cd "onelauncher-${pkgver}"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
|
||||
pnpm install --frozen-lockfile --no-optional
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "onelauncher-${pkgver}"
|
||||
pnpm --filter @onelauncher/gui build
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo build --frozen --release --all-features
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "onelauncher-${pkgver}"
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo test --frozen --all-features
|
||||
}
|
||||
|
||||
package() {
|
||||
install -Dm755 "$srcdir"/onelauncher-"$pkgver"/target/release/onelauncher_gui "$pkgdir"/usr/bin/onelauncher
|
||||
install -Dm644 "$srcdir"/onelauncher-"$pkgver"/packages/distribution/icons/128x128.png "$pkgdir"/usr/share/icons/hicolor/128x128/apps/OneLauncher.png
|
||||
install -Dm644 "$srcdir"/onelauncher-"$pkgver"/packages/distribution/icons/icon.png "$pkgdir"/usr/share/icons/hicolor/256x256@2/apps/OneLauncher.png
|
||||
install -Dm644 OneLauncher.desktop "$pkgdir"/usr/share/applications/OneLauncher.desktop
|
||||
}
|
||||
28
apps/oneclient/distribution/brew/onelauncher.rb
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
cask "onelauncher" do
|
||||
version "0.1.0"
|
||||
sha256 ""
|
||||
|
||||
url "https://launcher.polyfrost.org/versions/#{version}/macos/OneLauncher_#{version}_universal.dmg"
|
||||
name "OneLauncher"
|
||||
desc "Next-generation open source Minecraft launcher"
|
||||
homepage "https://polyfrost.org"
|
||||
|
||||
livecheck do
|
||||
url "https://polyfrost.org/launcher"
|
||||
regex("/OneLauncher[._-]v(\d+(?:\.\d+)+)[._-]universal\.dmg/i")
|
||||
end
|
||||
|
||||
auto_updated true
|
||||
depends_on macos: ">= :high_sierra"
|
||||
|
||||
app "OneLauncher.app"
|
||||
|
||||
uninstall quit: "OneLauncher"
|
||||
|
||||
zap trash: [
|
||||
"~/Library/Application Support/OneLauncher",
|
||||
"~/Library/Caches/OneLauncher",
|
||||
"~/Library/Saved Application State/OneLauncher.savedState",
|
||||
"~/Library/WebKit/OneLauncher",
|
||||
]
|
||||
end
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Name=OneLauncher
|
||||
Comment=Next-generation open source Minecraft launcher
|
||||
Exec=onelauncher
|
||||
Icon=org.polyfrost.launcher
|
||||
Type=Application
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
Categories=Game;
|
||||
Keywords=game;launcher;mc;minecraft;
|
||||
StartupWMClass=OneLauncher
|
||||
Version=1.4
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
|
||||
</component>
|
||||
BIN
apps/oneclient/distribution/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
apps/oneclient/distribution/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/oneclient/distribution/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
apps/oneclient/distribution/icons/512x512.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
apps/oneclient/distribution/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
apps/oneclient/distribution/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
apps/oneclient/distribution/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
apps/oneclient/distribution/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/oneclient/distribution/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
apps/oneclient/distribution/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
apps/oneclient/distribution/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
apps/oneclient/distribution/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
apps/oneclient/distribution/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
apps/oneclient/distribution/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
apps/oneclient/distribution/icons/icon.icns
Normal file
|
After Width: | Height: | Size: 469 KiB |
BIN
apps/oneclient/distribution/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
apps/oneclient/distribution/icons/icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Name=OneLauncher
|
||||
Comment=Next-generation open source Minecraft launcher
|
||||
Exec={{exec}}
|
||||
Icon={{icon}}
|
||||
Type=Application
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
Categories=Game;ActionGame;AdventureGame;Simulation;
|
||||
Keywords=mc;minecraft;game
|
||||
StartupWMClass={{exec}}
|
||||
Version=1.4
|
||||
28
apps/oneclient/distribution/scoop/onelauncher.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://polyfrost.org/launcher",
|
||||
"description": "Next-generation open source Minecraft launcher",
|
||||
"license": "GPL-3.0-only",
|
||||
"architecture": {
|
||||
"64bit": {
|
||||
"url": "https://launcher.polyfrost.org/versions/0.1.0/windows/OneLauncher_0.1.0_x64_en-US.msi",
|
||||
"hash": "hhjgskdfghskfdlafjdshjadlk"
|
||||
}
|
||||
},
|
||||
"shortcuts": [
|
||||
[
|
||||
"PFiles\\OneLauncher\\OneLauncher.exe",
|
||||
"OneLauncher"
|
||||
]
|
||||
],
|
||||
"checkver": {
|
||||
"github": "https://github.com/polyfrost/onelauncher"
|
||||
},
|
||||
"autoupdat": {
|
||||
"architecture": {
|
||||
"64bit": {
|
||||
"url": "https://launcher.polyfrost.org/versions/$version/windows/OneLauncher_$version_x64_en-US.msi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.5.0.schema.json
|
||||
|
||||
PackageIdentifier: Polyfrost.OneLauncher
|
||||
PackageVersion: 0.1.0
|
||||
InstallerLocale: en-US
|
||||
InstallerType: wix
|
||||
ProductCode: '{}'
|
||||
Installers:
|
||||
- Architecture: x64
|
||||
InstallerUrl: https://launcher.polyfrost.org/versions/0.1.0/windows/OneLauncher_0.1.0_x64_en-US.msi
|
||||
InstallerSha256: '1111111111111111111111111111318274823726424624666666666666666666666666666666661111E1235666'
|
||||
ManifestType: installer
|
||||
ManifestVersion: 1.5.0
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.5.0.schema.json
|
||||
|
||||
PackageIdentifier: Polyfrost.OneLauncher
|
||||
PackageVersion: 0.1.0
|
||||
PackageLocale: en-US
|
||||
Publisher: Polyfrost
|
||||
PackageName: OneLauncher
|
||||
License: GPL-3.0
|
||||
ShortDescription: Next-generation open source Minecraft launcher
|
||||
ManifestType: defaultLocale
|
||||
ManifestVersion: 1.5.0
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.5.0.schema.json
|
||||
|
||||
PackageIdentifier: Polyfrost.OneLauncher
|
||||
PackageVersion: 0.1.0
|
||||
DefaultLocale: en-US
|
||||
ManifestType: version
|
||||
ManifestVersion: 1.5.0
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --port 3000",
|
||||
"start": "vite --port 3000",
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"build": "vite build && tsc",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint",
|
||||
|
|
@ -12,6 +12,8 @@
|
|||
"check": "prettier --write . && eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/poppins": "^5.2.6",
|
||||
"@formatjs/intl-durationformat": "^0.7.4",
|
||||
"@tailwindcss/vite": "^4.0.6",
|
||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||
"@tanstack/react-query": "^5.66.5",
|
||||
|
|
@ -20,16 +22,28 @@
|
|||
"@tanstack/react-router-devtools": "^1.114.3",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@tanstack/router-plugin": "^1.114.3",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": "^2.2.2",
|
||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||
"@tauri-apps/plugin-shell": "^2.2.1",
|
||||
"@untitled-theme/icons-react": "^0.14.0",
|
||||
"motion": "^12.10.5",
|
||||
"overlayscrollbars": "^2.11.2",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"react": "^19.0.0",
|
||||
"react-aria-components": "^1.8.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwindcss": "^4.0.6"
|
||||
"tailwind-merge": "^3.2.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.6",
|
||||
"tailwindcss-animated": "^2.0.0",
|
||||
"tailwindcss-react-aria-components": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/eslint-config": "^0.1.0",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^6.1.0"
|
||||
}
|
||||
|
|
|
|||
233
apps/oneclient/frontend/src/bindings.gen.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
/** user-defined commands */
|
||||
|
||||
/** tauri-specta globals */
|
||||
|
||||
import type { WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow';
|
||||
import {
|
||||
invoke as TAURI_INVOKE,
|
||||
} from '@tauri-apps/api/core';
|
||||
import * as TAURI_API_EVENT from '@tauri-apps/api/event';
|
||||
|
||||
export const commands = {
|
||||
async getUsers(): Promise<Result<Array<MinecraftCredentials>, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_users') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getUser(uuid: string): Promise<Result<MinecraftCredentials, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_user', { uuid }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async removeUser(uuid: string): Promise<Result<null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('remove_user', { uuid }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getDefaultUser(fallback: boolean | null): Promise<Result<MinecraftCredentials | null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_default_user', { fallback }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async setDefaultUser(uuid: string | null): Promise<Result<null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('set_default_user', { uuid }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async beginMsFlow(): Promise<Result<MinecraftCredentials | null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('begin_ms_flow') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async createCluster(options: CreateCluster): Promise<Result<Model, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('create_cluster', { options }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getClusters(): Promise<Result<Array<Model>, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_clusters') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getClusterById(id: bigint): Promise<Result<Model | null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_cluster_by_id', { id }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getGlobalProfile(): Promise<Result<SettingProfileModel, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_global_profile') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getProfileOrDefault(name: string | null): Promise<Result<SettingProfileModel, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_profile_or_default', { name }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async openDevTools(): Promise<void> {
|
||||
await TAURI_INVOKE('open_dev_tools');
|
||||
},
|
||||
};
|
||||
|
||||
/** user-defined events */
|
||||
|
||||
export const events = __makeEvents__<{
|
||||
launcherEvent: LauncherEvent;
|
||||
}>({
|
||||
launcherEvent: 'launcher-event',
|
||||
});
|
||||
|
||||
/** user-defined constants */
|
||||
|
||||
/** user-defined types */
|
||||
|
||||
export type ClusterStage = 'notready' | 'downloading' | 'repairing' | 'ready';
|
||||
export interface CreateCluster { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon_url: Icon | null }
|
||||
export type GameLoader = 'vanilla' | 'forge' | 'neoforge' | 'quilt' | 'fabric' | 'legacyfabric';
|
||||
export type Icon = string;
|
||||
export interface IngressPayload { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number }
|
||||
export type IngressType = { Download: { file_name: string } } | 'JavaPrepare' | 'JavaCheck' | 'JavaLocate' | 'MinecraftDownload' | { PrepareCluster: { cluster_name: string } };
|
||||
export type LauncherEvent = { Ingress: IngressPayload } | { Message: MessagePayload } | { Process: ProcessPayload };
|
||||
export type MessageLevel = 'Info' | 'Warn' | 'Error';
|
||||
export interface MessagePayload { level: MessageLevel; message: string }
|
||||
/**
|
||||
* A structure of all needed Minecraft credentials for logging in and account management.
|
||||
*/
|
||||
export interface MinecraftCredentials {
|
||||
/**
|
||||
* The uuid of the credentials.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The username of the Minecraft account.
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* The access token as a String.
|
||||
*/
|
||||
access_token: string;
|
||||
/**
|
||||
* The refresh token as a string for [`MinecraftState#refresh`].
|
||||
*/
|
||||
refresh_token: string;
|
||||
/**
|
||||
* The time that the access token expires as a [`DateTime<Utc>`].
|
||||
*/
|
||||
expires: string;
|
||||
|
||||
}
|
||||
export interface Model { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_pack_id: string | null; linked_pack_version: string | null }
|
||||
export interface Process { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
||||
export type ProcessPayload = { Starting: { command: string } } | { Started: { process: Process } } | { Stopped: { pid: number; exit_code: number } } | { Output: { pid: number; output: string } };
|
||||
export interface Resolution { width: number; height: number }
|
||||
export interface SerializableError { kind: 'CoreError' }
|
||||
export interface SettingProfileModel { name: string; java_id: bigint | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null }
|
||||
export interface SettingsOsExtra { enable_gamemode: boolean | null }
|
||||
|
||||
interface __EventObj__<T> {
|
||||
listen: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
||||
once: (
|
||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
||||
emit: null extends T
|
||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
||||
}
|
||||
|
||||
export type Result<T, E> =
|
||||
| { status: 'ok'; data: T }
|
||||
| { status: 'error'; error: E };
|
||||
|
||||
function __makeEvents__<T extends Record<string, any>>(
|
||||
mappings: Record<keyof T, string>,
|
||||
) {
|
||||
return new Proxy(
|
||||
{} as unknown as {
|
||||
[K in keyof T]: __EventObj__<T[K]> & {
|
||||
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
||||
};
|
||||
},
|
||||
{
|
||||
get: (_, event) => {
|
||||
const name = mappings[event as keyof T];
|
||||
|
||||
return new Proxy((() => {}) as any, {
|
||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
||||
listen: (arg: any) => window.listen(name, arg),
|
||||
once: (arg: any) => window.once(name, arg),
|
||||
emit: (arg: any) => window.emit(name, arg),
|
||||
}),
|
||||
get: (_, command: keyof __EventObj__<any>) => {
|
||||
switch (command) {
|
||||
case 'listen':
|
||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
||||
case 'once':
|
||||
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
||||
case 'emit':
|
||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
88
apps/oneclient/frontend/src/routeTree.gen.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/'
|
||||
id: '__root__' | '/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
|
|
@ -1,20 +1,32 @@
|
|||
import { defineConfig } from "vite";
|
||||
import viteReact from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { resolve } from 'node:path';
|
||||
import process from 'node:process';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
||||
import { resolve } from "node:path";
|
||||
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
|
||||
import viteReact from '@vitejs/plugin-react';
|
||||
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite({ autoCodeSplitting: true }), viteReact(), tailwindcss()],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
}
|
||||
plugins: [
|
||||
TanStackRouterVite({ autoCodeSplitting: true }),
|
||||
viteReact(),
|
||||
tailwindcss(),
|
||||
],
|
||||
clearScreen: false,
|
||||
server: {
|
||||
port: 8001,
|
||||
strictPort: true,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
envPrefix: ['VITE_', 'TAURI_ENV_'],
|
||||
build: {
|
||||
target: process.env.TAURI_ENV_PLATFORM === 'windows' ? 'chrome105' : 'safari13',
|
||||
minify: !process.env.TAURI_ENV_DEBUG ? 'esbuild' : false,
|
||||
sourcemap: !!process.env.TAURI_ENV_DEBUG,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -71,6 +71,56 @@ export const commands = {
|
|||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async createCluster(options: CreateCluster): Promise<Result<Model, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('create_cluster', { options }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getClusters(): Promise<Result<Array<Model>, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_clusters') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getClusterById(id: bigint): Promise<Result<Model | null, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_cluster_by_id', { id }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getGlobalProfile(): Promise<Result<SettingProfileModel, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_global_profile') };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async getProfileOrDefault(name: string | null): Promise<Result<SettingProfileModel, SerializableError>> {
|
||||
try {
|
||||
return { status: 'ok', data: await TAURI_INVOKE('get_profile_or_default', { name }) };
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof Error)
|
||||
throw e;
|
||||
else return { status: 'error', error: e as any };
|
||||
}
|
||||
},
|
||||
async openDevTools(): Promise<void> {
|
||||
await TAURI_INVOKE('open_dev_tools');
|
||||
},
|
||||
|
|
@ -88,6 +138,10 @@ export const events = __makeEvents__<{
|
|||
|
||||
/** user-defined types */
|
||||
|
||||
export type ClusterStage = 'notready' | 'downloading' | 'repairing' | 'ready';
|
||||
export interface CreateCluster { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon_url: Icon | null }
|
||||
export type GameLoader = 'vanilla' | 'forge' | 'neoforge' | 'quilt' | 'fabric' | 'legacyfabric';
|
||||
export type Icon = string;
|
||||
export interface IngressPayload { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number }
|
||||
export type IngressType = { Download: { file_name: string } } | 'JavaPrepare' | 'JavaCheck' | 'JavaLocate' | 'MinecraftDownload' | { PrepareCluster: { cluster_name: string } };
|
||||
export type LauncherEvent = { Ingress: IngressPayload } | { Message: MessagePayload } | { Process: ProcessPayload };
|
||||
|
|
@ -119,9 +173,13 @@ export interface MinecraftCredentials {
|
|||
expires: string;
|
||||
|
||||
}
|
||||
export interface Model { id: bigint; folder_name: string; stage: ClusterStage; created_at: string; group_id: bigint | null; name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; last_played: string | null; overall_played: bigint | null; icon_url: Icon | null; setting_profile_name: string | null; linked_pack_id: string | null; linked_pack_version: string | null }
|
||||
export interface Process { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
||||
export type ProcessPayload = { Starting: { command: string } } | { Started: { process: Process } } | { Stopped: { pid: number; exit_code: number } } | { Output: { pid: number; output: string } };
|
||||
export interface Resolution { width: number; height: number }
|
||||
export interface SerializableError { kind: 'CoreError' }
|
||||
export interface SettingProfileModel { name: string; java_id: bigint | null; res: Resolution | null; force_fullscreen: boolean | null; mem_max: number | null; launch_args: string | null; launch_env: string | null; hook_pre: string | null; hook_wrapper: string | null; hook_post: string | null; os_extra: SettingsOsExtra | null }
|
||||
export interface SettingsOsExtra { enable_gamemode: boolean | null }
|
||||
|
||||
interface __EventObj__<T> {
|
||||
listen: (
|
||||
|
|
|
|||
|
|
@ -11,13 +11,36 @@
|
|||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as OnboardingRouteImport } from './routes/onboarding/route'
|
||||
import { Route as AppRouteImport } from './routes/app/route'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as OnboardingIndexImport } from './routes/onboarding/index'
|
||||
import { Route as AppIndexImport } from './routes/app/index'
|
||||
import { Route as OnboardingSummaryImport } from './routes/onboarding/summary'
|
||||
import { Route as OnboardingLoginImport } from './routes/onboarding/login'
|
||||
import { Route as OnboardingLanguageImport } from './routes/onboarding/language'
|
||||
import { Route as OnboardingImportImport } from './routes/onboarding/import'
|
||||
import { Route as OnboardingCompleteImport } from './routes/onboarding/complete'
|
||||
import { Route as AppSettingsRouteImport } from './routes/app/settings/route'
|
||||
import { Route as AppClusterRouteImport } from './routes/app/cluster/route'
|
||||
import { Route as AppSettingsIndexImport } from './routes/app/settings/index'
|
||||
import { Route as AppClusterIndexImport } from './routes/app/cluster/index'
|
||||
import { Route as AppBrowserIndexImport } from './routes/app/browser/index'
|
||||
import { Route as AppSettingsMinecraftImport } from './routes/app/settings/minecraft'
|
||||
import { Route as AppSettingsAppearanceImport } from './routes/app/settings/appearance'
|
||||
import { Route as AppSettingsAccountsImport } from './routes/app/settings/accounts'
|
||||
import { Route as AppClusterWorldsImport } from './routes/app/cluster/worlds'
|
||||
import { Route as AppClusterSettingsImport } from './routes/app/cluster/settings'
|
||||
import { Route as AppClusterScreenshotsImport } from './routes/app/cluster/screenshots'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const OnboardingRouteRoute = OnboardingRouteImport.update({
|
||||
id: '/onboarding',
|
||||
path: '/onboarding',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AppRouteRoute = AppRouteImport.update({
|
||||
id: '/app',
|
||||
path: '/app',
|
||||
|
|
@ -30,18 +53,114 @@ const IndexRoute = IndexImport.update({
|
|||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingIndexRoute = OnboardingIndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppIndexRoute = AppIndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingSummaryRoute = OnboardingSummaryImport.update({
|
||||
id: '/summary',
|
||||
path: '/summary',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingLoginRoute = OnboardingLoginImport.update({
|
||||
id: '/login',
|
||||
path: '/login',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingLanguageRoute = OnboardingLanguageImport.update({
|
||||
id: '/language',
|
||||
path: '/language',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingImportRoute = OnboardingImportImport.update({
|
||||
id: '/import',
|
||||
path: '/import',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const OnboardingCompleteRoute = OnboardingCompleteImport.update({
|
||||
id: '/complete',
|
||||
path: '/complete',
|
||||
getParentRoute: () => OnboardingRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppSettingsRouteRoute = AppSettingsRouteImport.update({
|
||||
id: '/settings',
|
||||
path: '/settings',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppClusterRouteRoute = AppClusterRouteImport.update({
|
||||
id: '/cluster',
|
||||
path: '/cluster',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppSettingsIndexRoute = AppSettingsIndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => AppSettingsRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppClusterIndexRoute = AppClusterIndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => AppClusterRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppBrowserIndexRoute = AppBrowserIndexImport.update({
|
||||
id: '/browser/',
|
||||
path: '/browser/',
|
||||
getParentRoute: () => AppRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppSettingsMinecraftRoute = AppSettingsMinecraftImport.update({
|
||||
id: '/minecraft',
|
||||
path: '/minecraft',
|
||||
getParentRoute: () => AppSettingsRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppSettingsAppearanceRoute = AppSettingsAppearanceImport.update({
|
||||
id: '/appearance',
|
||||
path: '/appearance',
|
||||
getParentRoute: () => AppSettingsRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppSettingsAccountsRoute = AppSettingsAccountsImport.update({
|
||||
id: '/accounts',
|
||||
path: '/accounts',
|
||||
getParentRoute: () => AppSettingsRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppClusterWorldsRoute = AppClusterWorldsImport.update({
|
||||
id: '/worlds',
|
||||
path: '/worlds',
|
||||
getParentRoute: () => AppClusterRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppClusterSettingsRoute = AppClusterSettingsImport.update({
|
||||
id: '/settings',
|
||||
path: '/settings',
|
||||
getParentRoute: () => AppClusterRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AppClusterScreenshotsRoute = AppClusterScreenshotsImport.update({
|
||||
id: '/screenshots',
|
||||
path: '/screenshots',
|
||||
getParentRoute: () => AppClusterRouteRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
|
|
@ -60,6 +179,62 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof AppRouteImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/onboarding': {
|
||||
id: '/onboarding'
|
||||
path: '/onboarding'
|
||||
fullPath: '/onboarding'
|
||||
preLoaderRoute: typeof OnboardingRouteImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/app/cluster': {
|
||||
id: '/app/cluster'
|
||||
path: '/cluster'
|
||||
fullPath: '/app/cluster'
|
||||
preLoaderRoute: typeof AppClusterRouteImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/app/settings': {
|
||||
id: '/app/settings'
|
||||
path: '/settings'
|
||||
fullPath: '/app/settings'
|
||||
preLoaderRoute: typeof AppSettingsRouteImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/onboarding/complete': {
|
||||
id: '/onboarding/complete'
|
||||
path: '/complete'
|
||||
fullPath: '/onboarding/complete'
|
||||
preLoaderRoute: typeof OnboardingCompleteImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/onboarding/import': {
|
||||
id: '/onboarding/import'
|
||||
path: '/import'
|
||||
fullPath: '/onboarding/import'
|
||||
preLoaderRoute: typeof OnboardingImportImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/onboarding/language': {
|
||||
id: '/onboarding/language'
|
||||
path: '/language'
|
||||
fullPath: '/onboarding/language'
|
||||
preLoaderRoute: typeof OnboardingLanguageImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/onboarding/login': {
|
||||
id: '/onboarding/login'
|
||||
path: '/login'
|
||||
fullPath: '/onboarding/login'
|
||||
preLoaderRoute: typeof OnboardingLoginImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/onboarding/summary': {
|
||||
id: '/onboarding/summary'
|
||||
path: '/summary'
|
||||
fullPath: '/onboarding/summary'
|
||||
preLoaderRoute: typeof OnboardingSummaryImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/app/': {
|
||||
id: '/app/'
|
||||
path: '/'
|
||||
|
|
@ -67,6 +242,55 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof AppIndexImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/onboarding/': {
|
||||
id: '/onboarding/'
|
||||
path: '/'
|
||||
fullPath: '/onboarding/'
|
||||
preLoaderRoute: typeof OnboardingIndexImport
|
||||
parentRoute: typeof OnboardingRouteImport
|
||||
}
|
||||
'/app/cluster/screenshots': {
|
||||
id: '/app/cluster/screenshots'
|
||||
path: '/screenshots'
|
||||
fullPath: '/app/cluster/screenshots'
|
||||
preLoaderRoute: typeof AppClusterScreenshotsImport
|
||||
parentRoute: typeof AppClusterRouteImport
|
||||
}
|
||||
'/app/cluster/settings': {
|
||||
id: '/app/cluster/settings'
|
||||
path: '/settings'
|
||||
fullPath: '/app/cluster/settings'
|
||||
preLoaderRoute: typeof AppClusterSettingsImport
|
||||
parentRoute: typeof AppClusterRouteImport
|
||||
}
|
||||
'/app/cluster/worlds': {
|
||||
id: '/app/cluster/worlds'
|
||||
path: '/worlds'
|
||||
fullPath: '/app/cluster/worlds'
|
||||
preLoaderRoute: typeof AppClusterWorldsImport
|
||||
parentRoute: typeof AppClusterRouteImport
|
||||
}
|
||||
'/app/settings/accounts': {
|
||||
id: '/app/settings/accounts'
|
||||
path: '/accounts'
|
||||
fullPath: '/app/settings/accounts'
|
||||
preLoaderRoute: typeof AppSettingsAccountsImport
|
||||
parentRoute: typeof AppSettingsRouteImport
|
||||
}
|
||||
'/app/settings/appearance': {
|
||||
id: '/app/settings/appearance'
|
||||
path: '/appearance'
|
||||
fullPath: '/app/settings/appearance'
|
||||
preLoaderRoute: typeof AppSettingsAppearanceImport
|
||||
parentRoute: typeof AppSettingsRouteImport
|
||||
}
|
||||
'/app/settings/minecraft': {
|
||||
id: '/app/settings/minecraft'
|
||||
path: '/minecraft'
|
||||
fullPath: '/app/settings/minecraft'
|
||||
preLoaderRoute: typeof AppSettingsMinecraftImport
|
||||
parentRoute: typeof AppSettingsRouteImport
|
||||
}
|
||||
'/app/browser/': {
|
||||
id: '/app/browser/'
|
||||
path: '/browser'
|
||||
|
|
@ -74,17 +298,70 @@ declare module '@tanstack/react-router' {
|
|||
preLoaderRoute: typeof AppBrowserIndexImport
|
||||
parentRoute: typeof AppRouteImport
|
||||
}
|
||||
'/app/cluster/': {
|
||||
id: '/app/cluster/'
|
||||
path: '/'
|
||||
fullPath: '/app/cluster/'
|
||||
preLoaderRoute: typeof AppClusterIndexImport
|
||||
parentRoute: typeof AppClusterRouteImport
|
||||
}
|
||||
'/app/settings/': {
|
||||
id: '/app/settings/'
|
||||
path: '/'
|
||||
fullPath: '/app/settings/'
|
||||
preLoaderRoute: typeof AppSettingsIndexImport
|
||||
parentRoute: typeof AppSettingsRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
interface AppClusterRouteRouteChildren {
|
||||
AppClusterScreenshotsRoute: typeof AppClusterScreenshotsRoute
|
||||
AppClusterSettingsRoute: typeof AppClusterSettingsRoute
|
||||
AppClusterWorldsRoute: typeof AppClusterWorldsRoute
|
||||
AppClusterIndexRoute: typeof AppClusterIndexRoute
|
||||
}
|
||||
|
||||
const AppClusterRouteRouteChildren: AppClusterRouteRouteChildren = {
|
||||
AppClusterScreenshotsRoute: AppClusterScreenshotsRoute,
|
||||
AppClusterSettingsRoute: AppClusterSettingsRoute,
|
||||
AppClusterWorldsRoute: AppClusterWorldsRoute,
|
||||
AppClusterIndexRoute: AppClusterIndexRoute,
|
||||
}
|
||||
|
||||
const AppClusterRouteRouteWithChildren = AppClusterRouteRoute._addFileChildren(
|
||||
AppClusterRouteRouteChildren,
|
||||
)
|
||||
|
||||
interface AppSettingsRouteRouteChildren {
|
||||
AppSettingsAccountsRoute: typeof AppSettingsAccountsRoute
|
||||
AppSettingsAppearanceRoute: typeof AppSettingsAppearanceRoute
|
||||
AppSettingsMinecraftRoute: typeof AppSettingsMinecraftRoute
|
||||
AppSettingsIndexRoute: typeof AppSettingsIndexRoute
|
||||
}
|
||||
|
||||
const AppSettingsRouteRouteChildren: AppSettingsRouteRouteChildren = {
|
||||
AppSettingsAccountsRoute: AppSettingsAccountsRoute,
|
||||
AppSettingsAppearanceRoute: AppSettingsAppearanceRoute,
|
||||
AppSettingsMinecraftRoute: AppSettingsMinecraftRoute,
|
||||
AppSettingsIndexRoute: AppSettingsIndexRoute,
|
||||
}
|
||||
|
||||
const AppSettingsRouteRouteWithChildren =
|
||||
AppSettingsRouteRoute._addFileChildren(AppSettingsRouteRouteChildren)
|
||||
|
||||
interface AppRouteRouteChildren {
|
||||
AppClusterRouteRoute: typeof AppClusterRouteRouteWithChildren
|
||||
AppSettingsRouteRoute: typeof AppSettingsRouteRouteWithChildren
|
||||
AppIndexRoute: typeof AppIndexRoute
|
||||
AppBrowserIndexRoute: typeof AppBrowserIndexRoute
|
||||
}
|
||||
|
||||
const AppRouteRouteChildren: AppRouteRouteChildren = {
|
||||
AppClusterRouteRoute: AppClusterRouteRouteWithChildren,
|
||||
AppSettingsRouteRoute: AppSettingsRouteRouteWithChildren,
|
||||
AppIndexRoute: AppIndexRoute,
|
||||
AppBrowserIndexRoute: AppBrowserIndexRoute,
|
||||
}
|
||||
|
|
@ -93,44 +370,176 @@ const AppRouteRouteWithChildren = AppRouteRoute._addFileChildren(
|
|||
AppRouteRouteChildren,
|
||||
)
|
||||
|
||||
interface OnboardingRouteRouteChildren {
|
||||
OnboardingCompleteRoute: typeof OnboardingCompleteRoute
|
||||
OnboardingImportRoute: typeof OnboardingImportRoute
|
||||
OnboardingLanguageRoute: typeof OnboardingLanguageRoute
|
||||
OnboardingLoginRoute: typeof OnboardingLoginRoute
|
||||
OnboardingSummaryRoute: typeof OnboardingSummaryRoute
|
||||
OnboardingIndexRoute: typeof OnboardingIndexRoute
|
||||
}
|
||||
|
||||
const OnboardingRouteRouteChildren: OnboardingRouteRouteChildren = {
|
||||
OnboardingCompleteRoute: OnboardingCompleteRoute,
|
||||
OnboardingImportRoute: OnboardingImportRoute,
|
||||
OnboardingLanguageRoute: OnboardingLanguageRoute,
|
||||
OnboardingLoginRoute: OnboardingLoginRoute,
|
||||
OnboardingSummaryRoute: OnboardingSummaryRoute,
|
||||
OnboardingIndexRoute: OnboardingIndexRoute,
|
||||
}
|
||||
|
||||
const OnboardingRouteRouteWithChildren = OnboardingRouteRoute._addFileChildren(
|
||||
OnboardingRouteRouteChildren,
|
||||
)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/app': typeof AppRouteRouteWithChildren
|
||||
'/onboarding': typeof OnboardingRouteRouteWithChildren
|
||||
'/app/cluster': typeof AppClusterRouteRouteWithChildren
|
||||
'/app/settings': typeof AppSettingsRouteRouteWithChildren
|
||||
'/onboarding/complete': typeof OnboardingCompleteRoute
|
||||
'/onboarding/import': typeof OnboardingImportRoute
|
||||
'/onboarding/language': typeof OnboardingLanguageRoute
|
||||
'/onboarding/login': typeof OnboardingLoginRoute
|
||||
'/onboarding/summary': typeof OnboardingSummaryRoute
|
||||
'/app/': typeof AppIndexRoute
|
||||
'/onboarding/': typeof OnboardingIndexRoute
|
||||
'/app/cluster/screenshots': typeof AppClusterScreenshotsRoute
|
||||
'/app/cluster/settings': typeof AppClusterSettingsRoute
|
||||
'/app/cluster/worlds': typeof AppClusterWorldsRoute
|
||||
'/app/settings/accounts': typeof AppSettingsAccountsRoute
|
||||
'/app/settings/appearance': typeof AppSettingsAppearanceRoute
|
||||
'/app/settings/minecraft': typeof AppSettingsMinecraftRoute
|
||||
'/app/browser': typeof AppBrowserIndexRoute
|
||||
'/app/cluster/': typeof AppClusterIndexRoute
|
||||
'/app/settings/': typeof AppSettingsIndexRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/onboarding/complete': typeof OnboardingCompleteRoute
|
||||
'/onboarding/import': typeof OnboardingImportRoute
|
||||
'/onboarding/language': typeof OnboardingLanguageRoute
|
||||
'/onboarding/login': typeof OnboardingLoginRoute
|
||||
'/onboarding/summary': typeof OnboardingSummaryRoute
|
||||
'/app': typeof AppIndexRoute
|
||||
'/onboarding': typeof OnboardingIndexRoute
|
||||
'/app/cluster/screenshots': typeof AppClusterScreenshotsRoute
|
||||
'/app/cluster/settings': typeof AppClusterSettingsRoute
|
||||
'/app/cluster/worlds': typeof AppClusterWorldsRoute
|
||||
'/app/settings/accounts': typeof AppSettingsAccountsRoute
|
||||
'/app/settings/appearance': typeof AppSettingsAppearanceRoute
|
||||
'/app/settings/minecraft': typeof AppSettingsMinecraftRoute
|
||||
'/app/browser': typeof AppBrowserIndexRoute
|
||||
'/app/cluster': typeof AppClusterIndexRoute
|
||||
'/app/settings': typeof AppSettingsIndexRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
'/app': typeof AppRouteRouteWithChildren
|
||||
'/onboarding': typeof OnboardingRouteRouteWithChildren
|
||||
'/app/cluster': typeof AppClusterRouteRouteWithChildren
|
||||
'/app/settings': typeof AppSettingsRouteRouteWithChildren
|
||||
'/onboarding/complete': typeof OnboardingCompleteRoute
|
||||
'/onboarding/import': typeof OnboardingImportRoute
|
||||
'/onboarding/language': typeof OnboardingLanguageRoute
|
||||
'/onboarding/login': typeof OnboardingLoginRoute
|
||||
'/onboarding/summary': typeof OnboardingSummaryRoute
|
||||
'/app/': typeof AppIndexRoute
|
||||
'/onboarding/': typeof OnboardingIndexRoute
|
||||
'/app/cluster/screenshots': typeof AppClusterScreenshotsRoute
|
||||
'/app/cluster/settings': typeof AppClusterSettingsRoute
|
||||
'/app/cluster/worlds': typeof AppClusterWorldsRoute
|
||||
'/app/settings/accounts': typeof AppSettingsAccountsRoute
|
||||
'/app/settings/appearance': typeof AppSettingsAppearanceRoute
|
||||
'/app/settings/minecraft': typeof AppSettingsMinecraftRoute
|
||||
'/app/browser/': typeof AppBrowserIndexRoute
|
||||
'/app/cluster/': typeof AppClusterIndexRoute
|
||||
'/app/settings/': typeof AppSettingsIndexRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/app' | '/app/' | '/app/browser'
|
||||
fullPaths:
|
||||
| '/'
|
||||
| '/app'
|
||||
| '/onboarding'
|
||||
| '/app/cluster'
|
||||
| '/app/settings'
|
||||
| '/onboarding/complete'
|
||||
| '/onboarding/import'
|
||||
| '/onboarding/language'
|
||||
| '/onboarding/login'
|
||||
| '/onboarding/summary'
|
||||
| '/app/'
|
||||
| '/onboarding/'
|
||||
| '/app/cluster/screenshots'
|
||||
| '/app/cluster/settings'
|
||||
| '/app/cluster/worlds'
|
||||
| '/app/settings/accounts'
|
||||
| '/app/settings/appearance'
|
||||
| '/app/settings/minecraft'
|
||||
| '/app/browser'
|
||||
| '/app/cluster/'
|
||||
| '/app/settings/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/app' | '/app/browser'
|
||||
id: '__root__' | '/' | '/app' | '/app/' | '/app/browser/'
|
||||
to:
|
||||
| '/'
|
||||
| '/onboarding/complete'
|
||||
| '/onboarding/import'
|
||||
| '/onboarding/language'
|
||||
| '/onboarding/login'
|
||||
| '/onboarding/summary'
|
||||
| '/app'
|
||||
| '/onboarding'
|
||||
| '/app/cluster/screenshots'
|
||||
| '/app/cluster/settings'
|
||||
| '/app/cluster/worlds'
|
||||
| '/app/settings/accounts'
|
||||
| '/app/settings/appearance'
|
||||
| '/app/settings/minecraft'
|
||||
| '/app/browser'
|
||||
| '/app/cluster'
|
||||
| '/app/settings'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/app'
|
||||
| '/onboarding'
|
||||
| '/app/cluster'
|
||||
| '/app/settings'
|
||||
| '/onboarding/complete'
|
||||
| '/onboarding/import'
|
||||
| '/onboarding/language'
|
||||
| '/onboarding/login'
|
||||
| '/onboarding/summary'
|
||||
| '/app/'
|
||||
| '/onboarding/'
|
||||
| '/app/cluster/screenshots'
|
||||
| '/app/cluster/settings'
|
||||
| '/app/cluster/worlds'
|
||||
| '/app/settings/accounts'
|
||||
| '/app/settings/appearance'
|
||||
| '/app/settings/minecraft'
|
||||
| '/app/browser/'
|
||||
| '/app/cluster/'
|
||||
| '/app/settings/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AppRouteRoute: typeof AppRouteRouteWithChildren
|
||||
OnboardingRouteRoute: typeof OnboardingRouteRouteWithChildren
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AppRouteRoute: AppRouteRouteWithChildren,
|
||||
OnboardingRouteRoute: OnboardingRouteRouteWithChildren,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
|
|
@ -144,7 +553,8 @@ export const routeTree = rootRoute
|
|||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/app"
|
||||
"/app",
|
||||
"/onboarding"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
|
|
@ -153,17 +563,106 @@ export const routeTree = rootRoute
|
|||
"/app": {
|
||||
"filePath": "app/route.tsx",
|
||||
"children": [
|
||||
"/app/cluster",
|
||||
"/app/settings",
|
||||
"/app/",
|
||||
"/app/browser/"
|
||||
]
|
||||
},
|
||||
"/onboarding": {
|
||||
"filePath": "onboarding/route.tsx",
|
||||
"children": [
|
||||
"/onboarding/complete",
|
||||
"/onboarding/import",
|
||||
"/onboarding/language",
|
||||
"/onboarding/login",
|
||||
"/onboarding/summary",
|
||||
"/onboarding/"
|
||||
]
|
||||
},
|
||||
"/app/cluster": {
|
||||
"filePath": "app/cluster/route.tsx",
|
||||
"parent": "/app",
|
||||
"children": [
|
||||
"/app/cluster/screenshots",
|
||||
"/app/cluster/settings",
|
||||
"/app/cluster/worlds",
|
||||
"/app/cluster/"
|
||||
]
|
||||
},
|
||||
"/app/settings": {
|
||||
"filePath": "app/settings/route.tsx",
|
||||
"parent": "/app",
|
||||
"children": [
|
||||
"/app/settings/accounts",
|
||||
"/app/settings/appearance",
|
||||
"/app/settings/minecraft",
|
||||
"/app/settings/"
|
||||
]
|
||||
},
|
||||
"/onboarding/complete": {
|
||||
"filePath": "onboarding/complete.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/onboarding/import": {
|
||||
"filePath": "onboarding/import.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/onboarding/language": {
|
||||
"filePath": "onboarding/language.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/onboarding/login": {
|
||||
"filePath": "onboarding/login.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/onboarding/summary": {
|
||||
"filePath": "onboarding/summary.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/app/": {
|
||||
"filePath": "app/index.tsx",
|
||||
"parent": "/app"
|
||||
},
|
||||
"/onboarding/": {
|
||||
"filePath": "onboarding/index.tsx",
|
||||
"parent": "/onboarding"
|
||||
},
|
||||
"/app/cluster/screenshots": {
|
||||
"filePath": "app/cluster/screenshots.tsx",
|
||||
"parent": "/app/cluster"
|
||||
},
|
||||
"/app/cluster/settings": {
|
||||
"filePath": "app/cluster/settings.tsx",
|
||||
"parent": "/app/cluster"
|
||||
},
|
||||
"/app/cluster/worlds": {
|
||||
"filePath": "app/cluster/worlds.tsx",
|
||||
"parent": "/app/cluster"
|
||||
},
|
||||
"/app/settings/accounts": {
|
||||
"filePath": "app/settings/accounts.tsx",
|
||||
"parent": "/app/settings"
|
||||
},
|
||||
"/app/settings/appearance": {
|
||||
"filePath": "app/settings/appearance.tsx",
|
||||
"parent": "/app/settings"
|
||||
},
|
||||
"/app/settings/minecraft": {
|
||||
"filePath": "app/settings/minecraft.tsx",
|
||||
"parent": "/app/settings"
|
||||
},
|
||||
"/app/browser/": {
|
||||
"filePath": "app/browser/index.tsx",
|
||||
"parent": "/app"
|
||||
},
|
||||
"/app/cluster/": {
|
||||
"filePath": "app/cluster/index.tsx",
|
||||
"parent": "/app/cluster"
|
||||
},
|
||||
"/app/settings/": {
|
||||
"filePath": "app/settings/index.tsx",
|
||||
"parent": "/app/settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{path::PathBuf, time::Duration};
|
|||
use onelauncher_migration::MigratorTrait;
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
|
||||
use crate::{utils::io::IOError, LauncherResult};
|
||||
use crate::{utils::io::{self, IOError}, LauncherResult};
|
||||
|
||||
use super::Dirs;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use std::sync::Arc;
|
|||
use sea_orm::DatabaseConnection;
|
||||
use tokio::sync::{OnceCell, RwLock};
|
||||
|
||||
use crate::store::Dirs;
|
||||
use crate::LauncherResult;
|
||||
|
||||
use super::credentials::CredentialsStore;
|
||||
|
|
@ -41,6 +42,8 @@ impl State {
|
|||
|
||||
#[tracing::instrument]
|
||||
async fn initialize() -> LauncherResult<Arc<Self>> {
|
||||
crate::utils::io::create_dir_all(Dirs::get().await?.base_dir()).await?;
|
||||
|
||||
let ingress_processor = IngressProcessor::new();
|
||||
let settings = Settings::new().await;
|
||||
let db = super::db::create_pool().await?;
|
||||
|
|
|
|||
48
pnpm-lock.yaml
generated
|
|
@ -75,6 +75,12 @@ importers:
|
|||
|
||||
apps/oneclient/frontend:
|
||||
dependencies:
|
||||
'@fontsource/poppins':
|
||||
specifier: ^5.2.6
|
||||
version: 5.2.6
|
||||
'@formatjs/intl-durationformat':
|
||||
specifier: ^0.7.4
|
||||
version: 0.7.4
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.0.6
|
||||
version: 4.1.5(vite@6.3.4(@types/node@22.7.9)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
|
|
@ -99,15 +105,54 @@ importers:
|
|||
'@tanstack/router-plugin':
|
||||
specifier: ^1.114.3
|
||||
version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.7.9)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)))(vite@6.3.4(@types/node@22.7.9)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
'@tauri-apps/plugin-clipboard-manager':
|
||||
specifier: ^2.2.2
|
||||
version: 2.2.2
|
||||
'@tauri-apps/plugin-dialog':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@tauri-apps/plugin-shell':
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1
|
||||
'@untitled-theme/icons-react':
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0(@types/react@19.1.2)(react@19.1.0)
|
||||
motion:
|
||||
specifier: ^12.10.5
|
||||
version: 12.10.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
overlayscrollbars:
|
||||
specifier: ^2.11.2
|
||||
version: 2.11.2
|
||||
overlayscrollbars-react:
|
||||
specifier: ^0.5.6
|
||||
version: 0.5.6(overlayscrollbars@2.11.2)(react@19.1.0)
|
||||
react:
|
||||
specifier: ^19.0.0
|
||||
version: 19.1.0
|
||||
react-aria-components:
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.1.0(react@19.1.0)
|
||||
tailwind-merge:
|
||||
specifier: ^3.2.0
|
||||
version: 3.2.0
|
||||
tailwind-variants:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(tailwindcss@4.1.5)
|
||||
tailwindcss:
|
||||
specifier: ^4.0.6
|
||||
version: 4.1.5
|
||||
tailwindcss-animated:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(tailwindcss@4.1.5)
|
||||
tailwindcss-react-aria-components:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(tailwindcss@4.1.5)
|
||||
devDependencies:
|
||||
'@tanstack/eslint-config':
|
||||
specifier: ^0.1.0
|
||||
|
|
@ -121,9 +166,6 @@ importers:
|
|||
'@vitejs/plugin-react':
|
||||
specifier: ^4.3.4
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.7.9)(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.5.3
|
||||
typescript:
|
||||
specifier: ^5.7.2
|
||||
version: 5.8.3
|
||||
|
|
|
|||