chore: move shared code from onelauncher to its own package
This commit is contained in:
parent
521b3cfdc9
commit
7d4acc2468
87 changed files with 2460 additions and 2262 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3374,6 +3374,7 @@ dependencies = [
|
||||||
"tauri-plugin-single-instance",
|
"tauri-plugin-single-instance",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-window-state",
|
"tauri-plugin-window-state",
|
||||||
|
"taurpc",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ tauri-plugin-dialog = { workspace = true }
|
||||||
tauri-plugin-deep-link = { workspace = true }
|
tauri-plugin-deep-link = { workspace = true }
|
||||||
|
|
||||||
# code gen
|
# code gen
|
||||||
|
taurpc = { workspace = true }
|
||||||
specta = { workspace = true }
|
specta = { workspace = true }
|
||||||
specta-typescript = { workspace = true }
|
specta-typescript = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,27 @@
|
||||||
// pub mod cluster;
|
use onelauncher_core::error::{DaoError, LauncherResult};
|
||||||
// pub mod users;
|
use tauri::Runtime;
|
||||||
// pub mod processor;
|
|
||||||
// pub mod package;
|
|
||||||
// pub mod other;
|
|
||||||
// pub mod settings;
|
|
||||||
|
|
||||||
// #[macro_export(local_inner_macros)]
|
#[taurpc::procedures(path = "oneclient", export_to = "../frontend/src/bindings.gen.ts")]
|
||||||
// macro_rules! collect_commands {
|
pub trait OneClientApi {
|
||||||
// () => {{
|
async fn return_error() -> LauncherResult<()>;
|
||||||
// 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,
|
async fn open_dev_tools<R: Runtime>(webview_window: tauri::WebviewWindow<R>);
|
||||||
// cluster::get_clusters,
|
}
|
||||||
// cluster::get_cluster_by_id,
|
|
||||||
|
|
||||||
// settings::get_global_profile,
|
#[taurpc::ipc_type]
|
||||||
// settings::get_profile_or_default,
|
pub struct OneClientApiImpl;
|
||||||
|
|
||||||
// other::open_dev_tools,
|
#[taurpc::resolvers]
|
||||||
// ]
|
impl OneClientApi for OneClientApiImpl {
|
||||||
// }};
|
|
||||||
// }
|
async fn return_error(self) -> LauncherResult<()> {
|
||||||
|
let err = DaoError::NotFound.into();
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_dev_tools<R: Runtime>(self, webview_window: tauri::WebviewWindow<R>) {
|
||||||
|
#[cfg(feature = "devtools")]
|
||||||
|
webview_window.open_devtools();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
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?)
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#[onelauncher_core::command]
|
|
||||||
#[allow(unused_parens)]
|
|
||||||
pub fn open_dev_tools(webview: tauri::WebviewWindow) {
|
|
||||||
#[cfg(feature = "devtools")]
|
|
||||||
webview.open_devtools();
|
|
||||||
}
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
// 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(())
|
|
||||||
// }
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// 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(())
|
|
||||||
// }
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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?)
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
// #[macro_export]
|
|
||||||
// macro_rules! collect_events {
|
|
||||||
// () => {{
|
|
||||||
// tauri_specta::collect_events! {
|
|
||||||
// onelauncher_core::api::proxy::event::LauncherEvent
|
|
||||||
// }
|
|
||||||
// }};
|
|
||||||
// }
|
|
||||||
|
|
@ -1,4 +1,2 @@
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod error;
|
|
||||||
pub mod events;
|
|
||||||
pub mod statics;
|
pub mod statics;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod specta;
|
pub mod specta;
|
||||||
pub mod updater;
|
// pub mod updater;
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,21 @@ use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn formatter(file: &Path) -> io::Result<()> {
|
pub fn formatter(file: &Path) -> io::Result<()> {
|
||||||
Command::new("pnpm")
|
let result = Command::new("pnpm")
|
||||||
.arg("eslint")
|
.arg("eslint")
|
||||||
.arg("--flag")
|
.arg("--flag")
|
||||||
.arg("unstable_ts_config")
|
.arg("unstable_ts_config")
|
||||||
.arg("--fix")
|
.arg("--fix")
|
||||||
.arg(file)
|
.arg(file)
|
||||||
.output()
|
.output();
|
||||||
.map(|_| ())
|
|
||||||
.map_err(io::Error::other)
|
match result {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error running formatter on {}: {}", file.display(), e);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const _: specta_typescript::FormatterFn = formatter;
|
const _: specta_typescript::FormatterFn = formatter;
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,117 @@
|
||||||
use onelauncher_core::store::Core;
|
// use onelauncher_core::store::Core;
|
||||||
use tauri::plugin::TauriPlugin;
|
// use tauri::plugin::TauriPlugin;
|
||||||
use tauri::{Emitter, Runtime};
|
// use tauri::{Emitter, Runtime};
|
||||||
use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
|
// use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
|
||||||
use tokio::sync::Mutex;
|
// use tokio::sync::Mutex;
|
||||||
|
|
||||||
#[derive(Debug, Clone, specta::Type, serde::Serialize)]
|
// #[derive(Debug, Clone, specta::Type, serde::Serialize)]
|
||||||
pub struct Update {
|
// pub struct Update {
|
||||||
pub version: String,
|
// pub version: String,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl Update {
|
// impl Update {
|
||||||
fn new(update: &TauriPluginUpdate) -> Self {
|
// fn new(update: &TauriPluginUpdate) -> Self {
|
||||||
Self {
|
// Self {
|
||||||
version: update.version.clone(),
|
// version: update.version.clone(),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Default)]
|
// #[derive(Default)]
|
||||||
pub struct State {
|
// pub struct State {
|
||||||
install_lock: Mutex<()>,
|
// install_lock: Mutex<()>,
|
||||||
}
|
// }
|
||||||
|
|
||||||
async fn get_update(app: tauri::AppHandle) -> Result<Option<TauriPluginUpdate>, String> {
|
// async fn get_update(app: tauri::AppHandle) -> Result<Option<TauriPluginUpdate>, String> {
|
||||||
app.updater_builder()
|
// app.updater_builder()
|
||||||
.header("X-OneClient-Version", "stable")
|
// .header("X-OneClient-Version", "stable")
|
||||||
.map_err(|e| e.to_string())?
|
// .map_err(|e| e.to_string())?
|
||||||
.build()
|
// .build()
|
||||||
.map_err(|e| e.to_string())?
|
// .map_err(|e| e.to_string())?
|
||||||
.check()
|
// .check()
|
||||||
.await
|
// .await
|
||||||
.map_err(|e| e.to_string())
|
// .map_err(|e| e.to_string())
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, specta::Type, tauri_specta::Event)]
|
// #[derive(Debug, Clone, serde::Serialize, specta::Type, tauri_specta::Event)]
|
||||||
#[serde(rename_all = "camelCase", tag = "status")]
|
// #[serde(rename_all = "camelCase", tag = "status")]
|
||||||
pub enum UpdateEvent {
|
// pub enum UpdateEvent {
|
||||||
Loading,
|
// Loading,
|
||||||
Error(String),
|
// Error(String),
|
||||||
UpdateAvailable { update: Update },
|
// UpdateAvailable { update: Update },
|
||||||
NoUpdateAvailable,
|
// NoUpdateAvailable,
|
||||||
Installing,
|
// Installing,
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[tauri::command]
|
// #[tauri::command]
|
||||||
#[specta::specta]
|
// #[specta::specta]
|
||||||
pub async fn check_for_update(app: tauri::AppHandle) -> Result<Option<Update>, String> {
|
// pub async fn check_for_update(app: tauri::AppHandle) -> Result<Option<Update>, String> {
|
||||||
app.emit("updater", UpdateEvent::Loading).ok();
|
// app.emit("updater", UpdateEvent::Loading).ok();
|
||||||
|
|
||||||
let update = match get_update(app.clone()).await {
|
// let update = match get_update(app.clone()).await {
|
||||||
Ok(update) => update,
|
// Ok(update) => update,
|
||||||
Err(e) => {
|
// Err(e) => {
|
||||||
app.emit("updater", UpdateEvent::Error(e.clone())).ok();
|
// app.emit("updater", UpdateEvent::Error(e.clone())).ok();
|
||||||
return Err(e);
|
// return Err(e);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
let update = update.map(|u| Update::new(&u));
|
// let update = update.map(|u| Update::new(&u));
|
||||||
|
|
||||||
app.emit(
|
// app.emit(
|
||||||
"updater",
|
// "updater",
|
||||||
update.clone().map_or(UpdateEvent::NoUpdateAvailable, |u| {
|
// update.clone().map_or(UpdateEvent::NoUpdateAvailable, |u| {
|
||||||
UpdateEvent::UpdateAvailable { update: u }
|
// UpdateEvent::UpdateAvailable { update: u }
|
||||||
}),
|
// }),
|
||||||
)
|
// )
|
||||||
.ok();
|
// .ok();
|
||||||
|
|
||||||
Ok(update)
|
// Ok(update)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[tauri::command]
|
// #[tauri::command]
|
||||||
#[specta::specta]
|
// #[specta::specta]
|
||||||
pub async fn install_update(
|
// pub async fn install_update(
|
||||||
app: tauri::AppHandle,
|
// app: tauri::AppHandle,
|
||||||
state: tauri::State<'_, State>,
|
// state: tauri::State<'_, State>,
|
||||||
) -> Result<(), String> {
|
// ) -> Result<(), String> {
|
||||||
let Ok(lock) = state.install_lock.try_lock() else {
|
// let Ok(lock) = state.install_lock.try_lock() else {
|
||||||
return Err("Update already installing".into());
|
// return Err("Update already installing".into());
|
||||||
};
|
// };
|
||||||
|
|
||||||
app.emit("updater", UpdateEvent::Installing).ok();
|
// app.emit("updater", UpdateEvent::Installing).ok();
|
||||||
|
|
||||||
get_update(app.clone())
|
// get_update(app.clone())
|
||||||
.await?
|
// .await?
|
||||||
.ok_or_else(|| "No update required".to_string())?
|
// .ok_or_else(|| "No update required".to_string())?
|
||||||
.download_and_install(|_, _| {}, || {})
|
// .download_and_install(|_, _| {}, || {})
|
||||||
.await
|
// .await
|
||||||
.map_err(|e| e.to_string())?;
|
// .map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
drop(lock);
|
// drop(lock);
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[must_use]
|
// #[must_use]
|
||||||
pub fn plugin<R: Runtime>() -> TauriPlugin<R> {
|
// pub fn plugin<R: Runtime>() -> TauriPlugin<R> {
|
||||||
tauri::plugin::Builder::new("onelauncher-updater")
|
// tauri::plugin::Builder::new("onelauncher-updater")
|
||||||
.on_page_load(|window, _| {
|
// .on_page_load(|window, _| {
|
||||||
#[cfg(target_os = "linux")]
|
// #[cfg(target_os = "linux")]
|
||||||
let updater_available = false;
|
// let updater_available = false;
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
// #[cfg(not(target_os = "linux"))]
|
||||||
let updater_available = true;
|
// let updater_available = true;
|
||||||
|
|
||||||
if updater_available {
|
// if updater_available {
|
||||||
window
|
// window
|
||||||
.eval("window.__LAUNCHER_UPDATER__ = true;")
|
// .eval("window.__LAUNCHER_UPDATER__ = true;")
|
||||||
.expect("Failed to inject updater JS");
|
// .expect("Failed to inject updater JS");
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
.js_init_script(format!(
|
// .js_init_script(format!(
|
||||||
r#"window.__ONECLIENT_VERSION__ = "{}";"#,
|
// r#"window.__ONECLIENT_VERSION__ = "{}";"#,
|
||||||
Core::get().launcher_version,
|
// Core::get().launcher_version,
|
||||||
))
|
// ))
|
||||||
.build()
|
// .build()
|
||||||
}
|
// }
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
use onelauncher_core::api::proxy::ProxyTauri;
|
use onelauncher_core::api::proxy::ProxyTauri;
|
||||||
|
use onelauncher_core::api::tauri::{TauriLauncherApi, TauriLauncherEventApi};
|
||||||
use onelauncher_core::error::LauncherResult;
|
use onelauncher_core::error::LauncherResult;
|
||||||
use onelauncher_core::store::proxy::ProxyState;
|
use onelauncher_core::store::proxy::ProxyState;
|
||||||
use onelauncher_core::store::semaphore::SemaphoreStore;
|
use onelauncher_core::store::semaphore::SemaphoreStore;
|
||||||
use onelauncher_core::store::{Core, CoreOptions, Dirs, State};
|
use onelauncher_core::store::{Core, CoreOptions, Dirs, State};
|
||||||
use tauri::{Emitter, Manager};
|
use tauri::{Emitter, Manager};
|
||||||
|
|
||||||
|
use crate::api::commands::OneClientApi;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
// pub mod ext;
|
pub mod ext;
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize)]
|
#[derive(Clone, serde::Serialize)]
|
||||||
pub struct SingleInstancePayload {
|
pub struct SingleInstancePayload {
|
||||||
|
|
@ -38,19 +41,15 @@ async fn initialize_core() -> LauncherResult<()> {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult<tauri::App> {
|
async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult<tauri::App> {
|
||||||
// let prebuild = tauri_specta::Builder::<tauri::Wry>::new()
|
let router = taurpc::Router::new()
|
||||||
// .commands(collect_commands!())
|
.export_config(
|
||||||
// .events(collect_events!());
|
specta_typescript::Typescript::default()
|
||||||
|
.bigint(specta_typescript::BigIntExportBehavior::BigInt)
|
||||||
// #[cfg(debug_assertions)]
|
.formatter(ext::specta::formatter),
|
||||||
// prebuild
|
)
|
||||||
// .export(
|
.merge(api::commands::OneClientApiImpl.into_handler())
|
||||||
// specta_typescript::Typescript::default()
|
.merge(onelauncher_core::api::tauri::TauriLauncherApiImpl.into_handler())
|
||||||
// .bigint(specta_typescript::BigIntExportBehavior::BigInt)
|
.merge(onelauncher_core::api::tauri::TauriLauncherEventApiImpl.into_handler());
|
||||||
// .formatter(ext::specta::formatter),
|
|
||||||
// "../frontend/src/bindings.gen.ts",
|
|
||||||
// )
|
|
||||||
// .expect("failed to export debug bindings!");
|
|
||||||
|
|
||||||
let builder = builder
|
let builder = builder
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||||
|
|
@ -60,15 +59,11 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
||||||
}))
|
}))
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
// .plugin(ext::updater::plugin())
|
|
||||||
// .manage(ext::updater::State::default())
|
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
// .plugin(api::init())
|
|
||||||
.menu(tauri::menu::Menu::new)
|
.menu(tauri::menu::Menu::new)
|
||||||
// .invoke_handler(prebuild.invoke_handler())
|
.invoke_handler(router.into_handler())
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
// prebuild.mount_events(app.handle());
|
|
||||||
setup_window(app.handle()).expect("failed to setup main window");
|
setup_window(app.handle()).expect("failed to setup main window");
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/poppins": "^5.2.6",
|
"@fontsource/poppins": "^5.2.6",
|
||||||
"@formatjs/intl-durationformat": "^0.7.4",
|
"@formatjs/intl-durationformat": "^0.7.4",
|
||||||
|
"@onelauncher/common": "workspace:*",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||||
"@tanstack/react-query": "^5.66.5",
|
"@tanstack/react-query": "^5.66.5",
|
||||||
|
|
@ -37,7 +38,8 @@
|
||||||
"tailwind-variants": "^1.0.0",
|
"tailwind-variants": "^1.0.0",
|
||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.0.6",
|
||||||
"tailwindcss-animated": "^2.0.0",
|
"tailwindcss-animated": "^2.0.0",
|
||||||
"tailwindcss-react-aria-components": "^2.0.0"
|
"tailwindcss-react-aria-components": "^2.0.0",
|
||||||
|
"taurpc": "^1.8.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/eslint-config": "^0.1.0",
|
"@tanstack/eslint-config": "^0.1.0",
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,65 @@
|
||||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
// This file has been generated by Specta. DO NOT EDIT.
|
||||||
|
|
||||||
/** user-defined commands */
|
import type { InferCommandOutput } from 'taurpc';
|
||||||
|
import { createTauRPCProxy as createProxy } from 'taurpc';
|
||||||
|
|
||||||
/** tauri-specta globals */
|
type TAURI_CHANNEL<T> = (response: T) => void;
|
||||||
|
|
||||||
import type { WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow';
|
export type ClusterError = { InvalidVersion: string } | 'MissingJavaVersion' | 'ClusterDownloading' | 'ClusterAlreadyRunning';
|
||||||
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 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 interface CreateCluster { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null }
|
||||||
|
|
||||||
|
export type CryptoError = { InvalidHash: { algorithm: HashAlgorithm; expected: string; actual: string } } | 'InvalidAlgorithm';
|
||||||
|
|
||||||
|
export type DaoError = 'NotFound' | 'AlreadyExists' | { InvalidValue: [string, string] };
|
||||||
|
|
||||||
|
export type DirectoryError = 'BaseDir';
|
||||||
|
|
||||||
|
export type DiscordError = 'MissingClientId' | 'ConnectError';
|
||||||
|
|
||||||
export type GameLoader = 'vanilla' | 'forge' | 'neoforge' | 'quilt' | 'fabric' | 'legacyfabric';
|
export type GameLoader = 'vanilla' | 'forge' | 'neoforge' | 'quilt' | 'fabric' | 'legacyfabric';
|
||||||
|
|
||||||
|
export type HashAlgorithm = 'Sha1' | 'Sha256' | 'Md5';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around generic and unhelpful [`std::io::Error`] messages.
|
||||||
|
*/
|
||||||
|
export type IOError = { InvalidAbsolutePath: string } | { IOErrorWrapper: { path: string } } | 'IOError' | 'DeserializeError' | 'AsyncZipError';
|
||||||
|
|
||||||
export type Icon = string;
|
export type Icon = string;
|
||||||
|
|
||||||
|
export type IncompatiblePackageType = 'McVersion' | 'Loader';
|
||||||
|
|
||||||
|
export type IngressError = 'NotFound';
|
||||||
|
|
||||||
export interface IngressPayload { id: string; message: string; ingress_type: IngressType; percent: number | null; total: number }
|
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 IngressType = { Download: { file_name: string } } | 'JavaPrepare' | 'JavaCheck' | 'JavaLocate' | 'MinecraftDownload' | { PrepareCluster: { cluster_name: string } };
|
||||||
|
|
||||||
|
export type JavaError = { ParseVersion: [string] } | 'Execute' | 'MissingJava';
|
||||||
|
|
||||||
|
export type LauncherError = { type: 'DirError'; data: DirectoryError } | { type: 'IOError'; data: IOError } | { type: 'IngressError'; data: IngressError } | { type: 'JavaError'; data: JavaError } | { type: 'CryptoError'; data: CryptoError } | { type: 'DiscordError'; data: DiscordError } | { type: 'MetadataError'; data: MetadataError } | { type: 'ClusterError'; data: ClusterError } | { type: 'MinecraftAuthError'; data: MinecraftAuthError } | { type: 'ProcessError'; data: ProcessError } | { type: 'PackageError'; data: PackageError } | { type: 'DaoError'; data: DaoError } | { type: 'SerdeError' } | { type: 'AnyhowError' } | { type: 'DbError' } | { type: 'ReqwestError' } | { type: 'InterpulseError' } | { type: 'RegexError' } | { type: 'SemaphoreError' } | { type: 'UrlError' } | { type: 'TauriError' };
|
||||||
|
|
||||||
export type LauncherEvent = { Ingress: IngressPayload } | { Message: MessagePayload } | { Process: ProcessPayload };
|
export type LauncherEvent = { Ingress: IngressPayload } | { Message: MessagePayload } | { Process: ProcessPayload };
|
||||||
|
|
||||||
export type MessageLevel = 'Info' | 'Warn' | 'Error';
|
export type MessageLevel = 'Info' | 'Warn' | 'Error';
|
||||||
|
|
||||||
export interface MessagePayload { level: MessageLevel; message: string }
|
export interface MessagePayload { level: MessageLevel; message: string }
|
||||||
|
|
||||||
|
export type MetadataError = 'FetchError' | { NotModdedManifest: GameLoader } | { NotVanillaManifest: GameLoader } | 'ParseError' | 'NoMatchingLoader' | 'NoMatchingVersion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around all `Error`s that can occur during the Microsoft authentication process.
|
||||||
|
*/
|
||||||
|
export type MinecraftAuthError = 'PublicKeyReading' | 'PKCS8Error' | { SerializeError: { step: MinecraftAuthStep } } | { DeserializeError: { step: MinecraftAuthStep; raw: string } } | { RequestError: { step: MinecraftAuthStep } } | { SigningError: { step: MinecraftAuthStep } } | 'HashError' | 'SessionIdError';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An ordered list of all MSA authentication steps.
|
||||||
|
*/
|
||||||
|
export type MinecraftAuthStep = 'DeviceToken' | 'SisuAuthenicate' | 'OAuthToken' | 'RefreshOAuthToken' | 'SisuAuthorize' | 'XstsAuthorize' | 'MinecraftToken' | 'MinecraftEntitlements' | 'MinecraftProfile';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A structure of all needed Minecraft credentials for logging in and account management.
|
* A structure of all needed Minecraft credentials for logging in and account management.
|
||||||
*/
|
*/
|
||||||
|
|
@ -173,61 +86,29 @@ export interface MinecraftCredentials {
|
||||||
expires: string;
|
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 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 type PackageError = 'NoPrimaryFile' | 'IsModPack' | { Incompatible: IncompatiblePackageType };
|
||||||
|
|
||||||
export interface Process { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
export interface Process { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
||||||
|
|
||||||
|
export type ProcessError = { HookUnsuccessful: number } | 'NoPid';
|
||||||
|
|
||||||
export type ProcessPayload = { Starting: { command: string } } | { Started: { process: Process } } | { Stopped: { pid: number; exit_code: number } } | { Output: { pid: number; output: 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 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 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 }
|
export interface SettingsOsExtra { enable_gamemode: boolean | null }
|
||||||
|
|
||||||
interface __EventObj__<T> {
|
const ARGS_MAP = { 'core': '{"get_cluster":["id"],"remove_cluster":["id"],"get_profile_or_default":["name"],"get_clusters":[],"remove_user":["uuid"],"get_global_profile":[],"get_users":[],"get_default_user":["fallback"],"create_cluster":["options"],"open_msa_login":[],"get_user":["uuid"],"set_default_user":["uuid"]}', 'oneclient': '{"open_dev_tools":[],"return_error":[]}', '': '{"send_event":["event"]}' };
|
||||||
listen: (
|
export interface Router {
|
||||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
'core': { get_clusters: () => Promise<Array<Model>>; get_cluster: (id: bigint) => Promise<Model | null>; remove_cluster: (id: bigint) => Promise<null>; create_cluster: (options: CreateCluster) => Promise<Model>; get_profile_or_default: (name: string | null) => Promise<SettingProfileModel>; get_global_profile: () => Promise<SettingProfileModel>; get_users: () => Promise<Array<MinecraftCredentials>>; get_user: (uuid: string) => Promise<MinecraftCredentials | null>; remove_user: (uuid: string) => Promise<null>; get_default_user: (fallback: boolean | null) => Promise<MinecraftCredentials | null>; set_default_user: (uuid: string | null) => Promise<null>; open_msa_login: () => Promise<MinecraftCredentials | null> };
|
||||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
'oneclient': { return_error: () => Promise<null>; open_dev_tools: () => Promise<void> };
|
||||||
once: (
|
'': { send_event: (event: LauncherEvent) => Promise<void> };
|
||||||
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> =
|
export type { InferCommandOutput };
|
||||||
| { status: 'ok'; data: T }
|
export const createTauRPCProxy = () => createProxy<Router>(ARGS_MAP);
|
||||||
| { 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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
import { Link } from '@tanstack/react-router';
|
|
||||||
|
|
||||||
export default function Header() {
|
|
||||||
return (
|
|
||||||
<header className="flex justify-between gap-2 bg-white p-2 text-black">
|
|
||||||
<nav className="flex flex-row">
|
|
||||||
<div className="px-2 font-bold">
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-2 font-bold">
|
|
||||||
<Link to="/demo/table">TanStack Table</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="px-2 font-bold">
|
|
||||||
<Link to="/demo/tanstack-query">TanStack Query</Link>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
14
apps/oneclient/frontend/src/fonts.ts
Normal file
14
apps/oneclient/frontend/src/fonts.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import '@fontsource/poppins/300.css';
|
||||||
|
import '@fontsource/poppins/300-italic.css';
|
||||||
|
import '@fontsource/poppins/400.css';
|
||||||
|
import '@fontsource/poppins/400-italic.css';
|
||||||
|
import '@fontsource/poppins/500.css';
|
||||||
|
import '@fontsource/poppins/500-italic.css';
|
||||||
|
import '@fontsource/poppins/600.css';
|
||||||
|
import '@fontsource/poppins/600-italic.css';
|
||||||
|
import '@fontsource/poppins/700.css';
|
||||||
|
import '@fontsource/poppins/700-italic.css';
|
||||||
|
import '@fontsource/poppins/800.css';
|
||||||
|
import '@fontsource/poppins/800-italic.css';
|
||||||
|
import '@fontsource/poppins/900.css';
|
||||||
|
import '@fontsource/poppins/900-italic.css';
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
||||||
|
|
||||||
export default function LayoutAddition() {
|
|
||||||
return <ReactQueryDevtools buttonPosition="bottom-right" />
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
|
||||||
|
|
||||||
export function getContext() {
|
|
||||||
return {
|
|
||||||
queryClient,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Provider({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
|
|
||||||
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.st0 {
|
|
||||||
fill: #9ae7fc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st1 {
|
|
||||||
fill: #61dafb;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
|
|
||||||
<circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
|
|
||||||
<path class="st1" d="M520.5,78.1"/>
|
|
||||||
</g>
|
|
||||||
<circle class="st0" cx="420.8" cy="296.6" r="43"/>
|
|
||||||
<path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/>
|
|
||||||
<path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/>
|
|
||||||
<path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/>
|
|
||||||
<path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/>
|
|
||||||
<path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/>
|
|
||||||
<path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/>
|
|
||||||
<path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/>
|
|
||||||
<path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/>
|
|
||||||
<path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/>
|
|
||||||
<path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/>
|
|
||||||
<path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/>
|
|
||||||
<path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/>
|
|
||||||
<path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/>
|
|
||||||
<path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/>
|
|
||||||
<path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/>
|
|
||||||
<path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/>
|
|
||||||
<path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/>
|
|
||||||
<path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/>
|
|
||||||
<path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/>
|
|
||||||
<path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/>
|
|
||||||
<path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/>
|
|
||||||
<path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.4 KiB |
|
|
@ -1,47 +1,62 @@
|
||||||
import { StrictMode } from 'react'
|
import { isDev, registerDevExperience, registerNativeExperience } from '@onelauncher/common';
|
||||||
import ReactDOM from 'react-dom/client'
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { RouterProvider, createRouter } from '@tanstack/react-router'
|
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
||||||
|
import { StrictMode } from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { createTauRPCProxy } from './bindings.gen';
|
||||||
|
import { routeTree } from './routeTree.gen';
|
||||||
|
|
||||||
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
|
import './fonts';
|
||||||
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
import './styles/global.css';
|
||||||
|
|
||||||
// Import the generated route tree
|
// Tauri bindings
|
||||||
import { routeTree } from './routeTree.gen'
|
export const bindings = createTauRPCProxy();
|
||||||
|
|
||||||
import './styles.css'
|
// Tanstack Query Client
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Create a new router instance
|
// Tanstack Router
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: {
|
context: {
|
||||||
...TanstackQuery.getContext(),
|
queryClient,
|
||||||
},
|
},
|
||||||
defaultPreload: 'intent',
|
defaultPreload: 'intent',
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
defaultStructuralSharing: true,
|
defaultStructuralSharing: true,
|
||||||
defaultPreloadStaleTime: 0,
|
defaultPreloadStaleTime: 0,
|
||||||
})
|
defaultNotFoundComponent: () => <div>Not Found</div>,
|
||||||
|
});
|
||||||
|
|
||||||
// Register the router instance for type safety
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: typeof router
|
router: typeof router;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register UX changes
|
||||||
|
if (isDev)
|
||||||
|
registerDevExperience();
|
||||||
|
|
||||||
|
registerNativeExperience();
|
||||||
|
|
||||||
// Render the app
|
// Render the app
|
||||||
const rootElement = document.getElementById('app')
|
const rootElement = document.getElementById('app');
|
||||||
if (rootElement && !rootElement.innerHTML) {
|
if (rootElement && !rootElement.innerHTML) {
|
||||||
const root = ReactDOM.createRoot(rootElement)
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(
|
root.render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<TanstackQuery.Provider>
|
<QueryClientProvider client={queryClient}>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</TanstackQuery.Provider>
|
</QueryClientProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
|
||||||
// to log results (for example: reportWebVitals(console.log))
|
|
||||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
|
||||||
reportWebVitals()
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,20 @@
|
||||||
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
import type { QueryClient } from '@tanstack/react-query';
|
||||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
|
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
|
||||||
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||||
import Header from '../components/Header'
|
|
||||||
|
|
||||||
import TanstackQueryLayout from '../integrations/tanstack-query/layout'
|
|
||||||
|
|
||||||
import type { QueryClient } from '@tanstack/react-query'
|
|
||||||
|
|
||||||
interface MyRouterContext {
|
interface MyRouterContext {
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||||
component: () => (
|
component: RootLayout,
|
||||||
<>
|
});
|
||||||
<Header />
|
|
||||||
|
|
||||||
<Outlet />
|
function RootLayout() {
|
||||||
<TanStackRouterDevtools />
|
return (
|
||||||
|
<>
|
||||||
<TanstackQueryLayout />
|
<Outlet />
|
||||||
</>
|
<TanStackRouterDevtools />
|
||||||
),
|
</>
|
||||||
})
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,15 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import logo from '../logo.svg'
|
import { Button } from "@onelauncher/common/components";
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
export const Route = createFileRoute('/')({
|
||||||
component: App,
|
component: App,
|
||||||
})
|
});
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<header className="min-h-screen flex flex-col items-center justify-center bg-[#282c34] text-white text-[calc(10px+2vmin)]">
|
<h1>test</h1>
|
||||||
<img
|
<Button>Click Me!</Button>
|
||||||
src={logo}
|
</div>
|
||||||
className="h-[40vmin] pointer-events-none animate-[spin_20s_linear_infinite]"
|
);
|
||||||
alt="logo"
|
|
||||||
/>
|
|
||||||
<p>
|
|
||||||
Edit <code>src/routes/index.tsx</code> and save to reload.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
className="text-[#61dafb] hover:underline"
|
|
||||||
href="https://reactjs.org"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn React
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
className="text-[#61dafb] hover:underline"
|
|
||||||
href="https://tanstack.com"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
Learn TanStack
|
|
||||||
</a>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
108
apps/oneclient/frontend/src/styles/global.css
Normal file
108
apps/oneclient/frontend/src/styles/global.css
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
@plugin "tailwindcss-react-aria-components";
|
||||||
|
@plugin "tailwindcss-animated";
|
||||||
|
@source "../../node_modules/@onelauncher/common";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* foreground colors */
|
||||||
|
--color-fg-primary: #d5dbff;
|
||||||
|
--color-fg-primary-hover: #dae0ff;
|
||||||
|
--color-fg-primary-pressed: #e1e5ff;
|
||||||
|
--color-fg-primary-disabled: #cdd1eb;
|
||||||
|
|
||||||
|
--color-fg-secondary: #78818d;
|
||||||
|
--color-fg-secondary-hover: #5f6874;
|
||||||
|
--color-fg-secondary-pressed: #828d9b;
|
||||||
|
--color-fg-secondary-disabled: #646d79;
|
||||||
|
|
||||||
|
--color-link: #61afef;
|
||||||
|
--color-link-hover: #5f87ff;
|
||||||
|
--color-link-pressed: #72afff;
|
||||||
|
--color-link-disabled: #7597b3;
|
||||||
|
|
||||||
|
/* background colors */
|
||||||
|
--color-brand: #2b4bff;
|
||||||
|
--color-brand-hover: #2843dd;
|
||||||
|
--color-brand-pressed: #3957ff;
|
||||||
|
--color-brand-disabled: #2543eb;
|
||||||
|
|
||||||
|
--color-component-bg: #1a2228;
|
||||||
|
--color-component-bg-hover: #1d242b;
|
||||||
|
--color-component-bg-pressed: #222c35;
|
||||||
|
--color-component-bg-disabled: #10181f;
|
||||||
|
|
||||||
|
--color-page: #11171c;
|
||||||
|
--color-page-elevated: #151c22;
|
||||||
|
--color-window-frame: #151b20;
|
||||||
|
|
||||||
|
/* general colors */
|
||||||
|
--color-danger: #ff4444;
|
||||||
|
--color-danger-hover: #d63434;
|
||||||
|
--color-danger-pressed: #ff5656;
|
||||||
|
--color-danger-disabled: #eb3030;
|
||||||
|
|
||||||
|
--color-success: #239a60;
|
||||||
|
--color-success-hover: #1a8752;
|
||||||
|
--color-success-pressed: #2cac6e;
|
||||||
|
--color-success-disabled: #0f864c;
|
||||||
|
|
||||||
|
--color-code-info: #61afef;
|
||||||
|
--color-code-warn: #e5c07b;
|
||||||
|
--color-code-error: #be5046;
|
||||||
|
--color-code-debug: #2b4bff;
|
||||||
|
--color-code-trace: #fdfdfd;
|
||||||
|
|
||||||
|
/* border */
|
||||||
|
--color-component-border: rgba(255, 255, 255, 0.05);
|
||||||
|
--color-component-border-hover: rgba(255, 255, 255, 0.10);
|
||||||
|
--color-component-border-pressed: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
/* overlay */
|
||||||
|
--color-ghost-overlay: rgba(255, 255, 255, 0.05);
|
||||||
|
--color-ghost-overlay-hover: rgba(255, 255, 255, 0.10);
|
||||||
|
|
||||||
|
--color-scrollbar-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--color-scrollbar-bg-hover: rgba(255, 255, 255, 0.10);
|
||||||
|
--color-scrollbar-bg-pressed: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
|
/* other */
|
||||||
|
--font-sans: "Poppins", sans-serif;
|
||||||
|
--text-xs: 0.688rem;
|
||||||
|
--text-sm: 0.75rem;
|
||||||
|
--text-base: 0.875rem;
|
||||||
|
--text-lg: 1rem;
|
||||||
|
--text-2lg: 1.125rem;
|
||||||
|
--text-xl: 1.25rem;
|
||||||
|
--text-xxl: 1.5rem;
|
||||||
|
--text-2xl: 2rem;
|
||||||
|
--text-3xl: 2.5rem;
|
||||||
|
--text-4xl: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
user-select: none !important;
|
||||||
|
-webkit-user-select: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply bg-page text-fg-primary text-base;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-scrollbar {
|
||||||
|
--os-size: 10px;
|
||||||
|
--os-track-border-radius: var(--border-radius-lg);
|
||||||
|
--os-handle-bg: var(--color-scrollbar-bg);
|
||||||
|
--os-handle-bg-hover: var(--color-scrollbar-bg-hover);
|
||||||
|
--os-handle-bg-active: var(--color-scrollbar-bg-pressed);
|
||||||
|
--os-handle-perpendicular-size-hover: 140%;
|
||||||
|
--os-handle-perpendicular-size-active: 140%;
|
||||||
|
--os-handle-interactive-area-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-scrollbar-offset-right {
|
||||||
|
--os-padding-axis: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-hide-horizontal-scrollbar > .os-scrollbar-horizontal {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
26
apps/oneclient/frontend/src/utils/theming.ts
Normal file
26
apps/oneclient/frontend/src/utils/theming.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
export const DEFAULT_THEME = 'OneLauncher-Dark';
|
||||||
|
export const THEMES = {
|
||||||
|
OneLauncher: {
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: 'Dark',
|
||||||
|
type: 'dark',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light',
|
||||||
|
type: 'light',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as const satisfies Record<string, Theme>;
|
||||||
|
|
||||||
|
export interface Theme {
|
||||||
|
variants: Array<ThemeVariant>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThemeTypes = 'dark' | 'light';
|
||||||
|
|
||||||
|
export interface ThemeVariant {
|
||||||
|
type: ThemeTypes;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/poppins": "^5.2.6",
|
"@fontsource/poppins": "^5.2.6",
|
||||||
"@formatjs/intl-durationformat": "^0.7.4",
|
"@formatjs/intl-durationformat": "^0.7.4",
|
||||||
|
"@onelauncher/common": "workspace:*",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.0.6",
|
||||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||||
"@tanstack/react-query": "^5.66.5",
|
"@tanstack/react-query": "^5.66.5",
|
||||||
|
|
|
||||||
|
|
@ -103,11 +103,11 @@ export interface SettingProfileModel { name: string; java_id: bigint | null; res
|
||||||
|
|
||||||
export interface SettingsOsExtra { enable_gamemode: boolean | null }
|
export interface SettingsOsExtra { enable_gamemode: boolean | null }
|
||||||
|
|
||||||
const ARGS_MAP = { 'onelauncher': '{"open_dev_tools":[],"return_error":[]}', '': '{"send_event":["event"]}', 'core': '{"get_clusters":[],"get_user":["uuid"],"remove_user":["uuid"],"create_cluster":["options"],"get_profile_or_default":["name"],"get_default_user":["fallback"],"set_default_user":["uuid"],"get_global_profile":[],"open_msa_login":[],"get_users":[],"get_cluster":["id"],"remove_cluster":["id"]}' };
|
const ARGS_MAP = { '': '{"send_event":["event"]}', 'onelauncher': '{"return_error":[],"open_dev_tools":[]}', 'core': '{"get_user":["uuid"],"get_default_user":["fallback"],"set_default_user":["uuid"],"get_global_profile":[],"get_clusters":[],"remove_cluster":["id"],"get_profile_or_default":["name"],"get_users":[],"remove_user":["uuid"],"open_msa_login":[],"create_cluster":["options"],"get_cluster":["id"]}' };
|
||||||
export interface Router {
|
export interface Router {
|
||||||
'onelauncher': { return_error: () => Promise<null>; open_dev_tools: () => Promise<void> };
|
'onelauncher': { return_error: () => Promise<null>; open_dev_tools: () => Promise<void> };
|
||||||
'': { send_event: (event: LauncherEvent) => Promise<void> };
|
|
||||||
'core': { get_clusters: () => Promise<Array<Model>>; get_cluster: (id: bigint) => Promise<Model | null>; remove_cluster: (id: bigint) => Promise<null>; create_cluster: (options: CreateCluster) => Promise<Model>; get_profile_or_default: (name: string | null) => Promise<SettingProfileModel>; get_global_profile: () => Promise<SettingProfileModel>; get_users: () => Promise<Array<MinecraftCredentials>>; get_user: (uuid: string) => Promise<MinecraftCredentials | null>; remove_user: (uuid: string) => Promise<null>; get_default_user: (fallback: boolean | null) => Promise<MinecraftCredentials | null>; set_default_user: (uuid: string | null) => Promise<null>; open_msa_login: () => Promise<MinecraftCredentials | null> };
|
'core': { get_clusters: () => Promise<Array<Model>>; get_cluster: (id: bigint) => Promise<Model | null>; remove_cluster: (id: bigint) => Promise<null>; create_cluster: (options: CreateCluster) => Promise<Model>; get_profile_or_default: (name: string | null) => Promise<SettingProfileModel>; get_global_profile: () => Promise<SettingProfileModel>; get_users: () => Promise<Array<MinecraftCredentials>>; get_user: (uuid: string) => Promise<MinecraftCredentials | null>; remove_user: (uuid: string) => Promise<null>; get_default_user: (fallback: boolean | null) => Promise<MinecraftCredentials | null>; set_default_user: (uuid: string | null) => Promise<null>; open_msa_login: () => Promise<MinecraftCredentials | null> };
|
||||||
|
'': { send_event: (event: LauncherEvent) => Promise<void> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { InferCommandOutput };
|
export type { InferCommandOutput };
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import type { ToOptions } from '@tanstack/react-router';
|
import type { ToOptions } from '@tanstack/react-router';
|
||||||
import useCommand from '@/hooks/useCommand';
|
import {useCommand} from '@onelauncher/common';
|
||||||
import { bindings } from '@/main';
|
import { bindings } from '@/main';
|
||||||
import { Link } from '@tanstack/react-router';
|
import { Link } from '@tanstack/react-router';
|
||||||
import { Bell01Icon, Cloud01Icon, TerminalBrowserIcon } from '@untitled-theme/icons-react';
|
import { Bell01Icon, Cloud01Icon, TerminalBrowserIcon } from '@untitled-theme/icons-react';
|
||||||
import { MenuTrigger } from 'react-aria-components';
|
import { MenuTrigger } from 'react-aria-components';
|
||||||
import Button from './base/Button';
|
import { Button, Menu } from '@onelauncher/common/components';
|
||||||
import Menu from './base/Menu';
|
|
||||||
import PlayerHead from './content/PlayerHead';
|
import PlayerHead from './content/PlayerHead';
|
||||||
import AccountPopup from './overlay/popups/AccountPopup';
|
import AccountPopup from './overlay/popups/AccountPopup';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,16 @@ interface ScrollableContainerProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ScrollableContainer(props: ScrollableContainerProps) {
|
function ScrollableContainer({ children }: ScrollableContainerProps) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full h-screen w-full flex flex-1 flex-col overflow-hidden">
|
<div className="h-full w-full flex flex-1 flex-col overflow-hidden">
|
||||||
<OverlayScrollbarsComponent className="os-hide-horizontal-scrollbar relative flex-1">
|
<OverlayScrollbarsComponent className="os-hide-horizontal-scrollbar relative flex-1">
|
||||||
<div className="absolute h-full w-[calc(100%-14px)] flex flex-col gap-2">
|
<div className="absolute h-full w-[calc(100%-14px)] flex flex-col gap-2">
|
||||||
{props.children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ScrollableContainer;
|
export default ScrollableContainer;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import type { ComponentType, JSX } from 'react';
|
import type { ComponentType, JSX } from 'react';
|
||||||
import type { PressEvent } from 'react-aria-components';
|
import type { PressEvent } from 'react-aria-components';
|
||||||
|
import { Button } from '@onelauncher/common/components';
|
||||||
import { Window } from '@tauri-apps/api/window';
|
import { Window } from '@tauri-apps/api/window';
|
||||||
import { ChevronLeftIcon, Maximize02Icon, Minimize01Icon, MinusIcon, XCloseIcon } from '@untitled-theme/icons-react';
|
import { ChevronLeftIcon, Maximize02Icon, Minimize01Icon, MinusIcon, XCloseIcon } from '@untitled-theme/icons-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
import Button from './base/Button';
|
|
||||||
|
|
||||||
function WindowFrame() {
|
function WindowFrame() {
|
||||||
const [isMaximized, setIsMaximized] = useState(false);
|
const [isMaximized, setIsMaximized] = useState(false);
|
||||||
|
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
import type { ClassNameString } from '@/types/global';
|
|
||||||
import type {
|
|
||||||
SelectProps as AriaComboBoxProps,
|
|
||||||
ListBoxItemProps as AriaListBoxItemProps
|
|
||||||
} from 'react-aria-components';
|
|
||||||
import {
|
|
||||||
Select as AriaComboBox,
|
|
||||||
Label as AriaLabel,
|
|
||||||
ListBox as AriaListBox,
|
|
||||||
ListBoxItem as AriaListBoxItem,
|
|
||||||
Popover as AriaPopover,
|
|
||||||
SelectValue
|
|
||||||
} from 'react-aria-components';
|
|
||||||
import { tv } from 'tailwind-variants';
|
|
||||||
import Button from './Button';
|
|
||||||
|
|
||||||
interface ComboBoxWrapperProps<T extends object> extends Omit<AriaComboBoxProps<T>, 'className'>, ClassNameString {
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownStyles = tv({
|
|
||||||
base: [
|
|
||||||
"flex flex-col border border-component-bg-hover w-fit rounded-lg focus:outline-none"
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
function Dropdown<T extends object>({
|
|
||||||
className,
|
|
||||||
label,
|
|
||||||
children,
|
|
||||||
...rest
|
|
||||||
}: ComboBoxWrapperProps<T>) {
|
|
||||||
return (
|
|
||||||
<AriaComboBox
|
|
||||||
className={dropdownStyles({ className })}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{label && <AriaLabel>{label}</AriaLabel>}
|
|
||||||
<div className="flex">
|
|
||||||
<SelectValue />
|
|
||||||
<Button className="size-6">▼</Button>
|
|
||||||
</div>
|
|
||||||
<AriaPopover className="border border-orange-400">
|
|
||||||
<AriaListBox className="bg-blue-500 p-2 w-38">
|
|
||||||
{children}
|
|
||||||
</AriaListBox>
|
|
||||||
</AriaPopover>
|
|
||||||
</AriaComboBox>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Dropdown;
|
|
||||||
|
|
||||||
Dropdown.Item = ({
|
|
||||||
className,
|
|
||||||
...rest
|
|
||||||
}: AriaListBoxItemProps & ClassNameString) => (
|
|
||||||
<AriaListBoxItem
|
|
||||||
className={className}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import React, { type JSX } from 'react';
|
|
||||||
|
|
||||||
interface ShowProps<T = any> {
|
|
||||||
/**
|
|
||||||
* Condition to evaluate.
|
|
||||||
* If truthy, `children` are rendered.
|
|
||||||
* If T is a value (not just boolean), you might want to use it in children,
|
|
||||||
* but this basic version doesn't automatically pass it like SolidJS does for type narrowing.
|
|
||||||
*/
|
|
||||||
when: T | boolean | undefined | null;
|
|
||||||
/**
|
|
||||||
* Content to render if `when` is falsy.
|
|
||||||
*/
|
|
||||||
fallback?: React.ReactNode;
|
|
||||||
/**
|
|
||||||
* Content to render if `when` is truthy.
|
|
||||||
*/
|
|
||||||
children: React.ReactNode | ((item: NonNullable<T>) => React.ReactNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ShowComponent = <T,>({ when, fallback = null, children }: ShowProps<T>): JSX.Element | null => {
|
|
||||||
if (Boolean(when)) {
|
|
||||||
if (typeof children === 'function') {
|
|
||||||
// Ensure 'when' is not null or undefined before calling the function.
|
|
||||||
// This mimics Solid's behavior where children can be a function that receives the truthy 'when' value.
|
|
||||||
return <>{children(when as NonNullable<T>)}</>;
|
|
||||||
}
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
return <>{fallback}</>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Show = React.memo(ShowComponent) as <T>(props: ShowProps<T>) => JSX.Element | null;
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import type React from 'react';
|
|
||||||
import type { RefAttributes } from 'react';
|
|
||||||
import type { SwitchProps as AriaSwitchProps } from 'react-aria-components';
|
|
||||||
import { Switch as AriaSwitch } from 'react-aria-components';
|
|
||||||
import { tv } from 'tailwind-variants';
|
|
||||||
|
|
||||||
export const switchVariants = tv({
|
|
||||||
base: [
|
|
||||||
'group inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
|
||||||
'bg-component-bg-pressed data-[selected]:bg-blue-600',
|
|
||||||
'focus:outline-none',
|
|
||||||
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const switchThumbVariants = tv({
|
|
||||||
base: [
|
|
||||||
'pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow ring-0 transition-transform',
|
|
||||||
'translate-x-1 group-data-[selected]:translate-x-6',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export type SwitchProps = AriaSwitchProps & RefAttributes<HTMLLabelElement> & {
|
|
||||||
className?: string;
|
|
||||||
children?: React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Switch({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: SwitchProps) {
|
|
||||||
return (
|
|
||||||
<AriaSwitch className={switchVariants({ className })} {...props}>
|
|
||||||
<span className={switchThumbVariants()} />
|
|
||||||
{children && <span className="ml-3 text-sm font-medium text-gray-900">{children}</span>}
|
|
||||||
</AriaSwitch>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Switch;
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import type React from 'react';
|
|
||||||
import type { RefAttributes } from 'react';
|
|
||||||
import type { TooltipTriggerComponentProps as AriaTooltipTriggerProps } from 'react-aria-components';
|
|
||||||
import {
|
|
||||||
TooltipTrigger as AriaTooltipTrigger,
|
|
||||||
Tooltip as AriaTooltip,
|
|
||||||
OverlayArrow
|
|
||||||
} from 'react-aria-components';
|
|
||||||
import { tv } from 'tailwind-variants';
|
|
||||||
|
|
||||||
export const tooltipVariants = tv({
|
|
||||||
base: [
|
|
||||||
'px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-lg',
|
|
||||||
'border border-gray-700 max-w-xs z-50',
|
|
||||||
'data-[entering]:animate-in data-[entering]:fade-in data-[entering]:zoom-in-95',
|
|
||||||
'data-[exiting]:animate-out data-[exiting]:fade-out data-[exiting]:zoom-out-95',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const tooltipArrowVariants = tv({
|
|
||||||
base: [
|
|
||||||
'fill-gray-900 stroke-gray-700 stroke-1',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export type TooltipProps = AriaTooltipTriggerProps & RefAttributes<HTMLElement> & {
|
|
||||||
text: string;
|
|
||||||
children: React.ReactElement;
|
|
||||||
placement?: 'top' | 'bottom' | 'left' | 'right';
|
|
||||||
className?: string;
|
|
||||||
delay?: number;
|
|
||||||
closeDelay?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Tooltip({
|
|
||||||
text,
|
|
||||||
children,
|
|
||||||
placement = 'top',
|
|
||||||
className,
|
|
||||||
delay = 700,
|
|
||||||
closeDelay = 0,
|
|
||||||
...props
|
|
||||||
}: TooltipProps) {
|
|
||||||
return (
|
|
||||||
<AriaTooltipTrigger delay={delay} closeDelay={closeDelay} {...props}>
|
|
||||||
{children}
|
|
||||||
<AriaTooltip placement={placement} className={tooltipVariants({ className })}>
|
|
||||||
<OverlayArrow>
|
|
||||||
<svg width={8} height={8} viewBox="0 0 8 8" className={tooltipArrowVariants()}>
|
|
||||||
<path d="M0 0 L4 4 L8 0" />
|
|
||||||
</svg>
|
|
||||||
</OverlayArrow>
|
|
||||||
{text}
|
|
||||||
</AriaTooltip>
|
|
||||||
</AriaTooltipTrigger>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Tooltip;
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import Button from '@/components/base/Button';
|
|
||||||
import Menu from '@/components/base/Menu';
|
|
||||||
import PlayerHead from '@/components/content/PlayerHead';
|
import PlayerHead from '@/components/content/PlayerHead';
|
||||||
import useCommand from '@/hooks/useCommand';
|
import {useCommand} from '@onelauncher/common';
|
||||||
|
import { Button, Menu } from '@onelauncher/common/components';
|
||||||
import { bindings } from '@/main';
|
import { bindings } from '@/main';
|
||||||
import { PlusIcon, Settings01Icon } from '@untitled-theme/icons-react';
|
import { PlusIcon, Settings01Icon } from '@untitled-theme/icons-react';
|
||||||
import { Separator } from 'react-aria-components';
|
import { Separator } from 'react-aria-components';
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
import type { bindings } from '@/main';
|
|
||||||
import type { UndefinedInitialDataOptions } from '@tanstack/react-query';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
|
|
||||||
type CacheKey = (keyof typeof bindings.core) | (keyof typeof bindings.onelauncher);
|
|
||||||
|
|
||||||
function useCommand<T>(cacheKey: CacheKey | (string & {}), command: () => Promise<T>, options?: Omit<UndefinedInitialDataOptions<T>, 'queryKey' | 'queryFn'>) {
|
|
||||||
return useQuery({
|
|
||||||
queryKey: [cacheKey],
|
|
||||||
queryFn: async () => {
|
|
||||||
try {
|
|
||||||
const result = await command();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
if (e instanceof Error)
|
|
||||||
return Promise.reject(e);
|
|
||||||
else
|
|
||||||
return Promise.reject(new Error('Unknown error'));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled: true,
|
|
||||||
...options,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default useCommand;
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3">
|
|
||||||
<!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) -->
|
|
||||||
<defs>
|
|
||||||
<style>
|
|
||||||
.st0 {
|
|
||||||
fill: #9ae7fc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.st1 {
|
|
||||||
fill: #61dafb;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</defs>
|
|
||||||
<g>
|
|
||||||
<path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/>
|
|
||||||
<circle class="st1" cx="420.9" cy="296.5" r="45.7"/>
|
|
||||||
<path class="st1" d="M520.5,78.1"/>
|
|
||||||
</g>
|
|
||||||
<circle class="st0" cx="420.8" cy="296.6" r="43"/>
|
|
||||||
<path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/>
|
|
||||||
<path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/>
|
|
||||||
<path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/>
|
|
||||||
<path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/>
|
|
||||||
<path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/>
|
|
||||||
<path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/>
|
|
||||||
<path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/>
|
|
||||||
<path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/>
|
|
||||||
<path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/>
|
|
||||||
<path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/>
|
|
||||||
<path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/>
|
|
||||||
<path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/>
|
|
||||||
<path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/>
|
|
||||||
<path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/>
|
|
||||||
<path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/>
|
|
||||||
<path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/>
|
|
||||||
<path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/>
|
|
||||||
<path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/>
|
|
||||||
<path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/>
|
|
||||||
<path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/>
|
|
||||||
<path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/>
|
|
||||||
<path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 8.4 KiB |
|
|
@ -1,14 +1,12 @@
|
||||||
|
import { isDev, registerDevExperience, registerNativeExperience } from '@onelauncher/common';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
import { createRouter, RouterProvider } from '@tanstack/react-router';
|
||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { createTauRPCProxy } from './bindings.gen';
|
import { createTauRPCProxy } from './bindings.gen';
|
||||||
|
|
||||||
import { routeTree } from './routeTree.gen';
|
import { routeTree } from './routeTree.gen';
|
||||||
|
|
||||||
import './fonts';
|
import './fonts';
|
||||||
import './utils/nativeExperience';
|
|
||||||
import './utils/devExperience';
|
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
import './styles/global.css';
|
import './styles/global.css';
|
||||||
|
|
||||||
|
|
@ -44,6 +42,12 @@ declare module '@tanstack/react-router' {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register UX changes
|
||||||
|
if (isDev)
|
||||||
|
registerDevExperience();
|
||||||
|
|
||||||
|
registerNativeExperience();
|
||||||
|
|
||||||
// Render the app
|
// Render the app
|
||||||
const rootElement = document.getElementById('app');
|
const rootElement = document.getElementById('app');
|
||||||
if (rootElement && !rootElement.innerHTML) {
|
if (rootElement && !rootElement.innerHTML) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { QueryClient } from '@tanstack/react-query';
|
import type { QueryClient } from '@tanstack/react-query';
|
||||||
import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
|
import type { NavigateOptions, ToOptions } from '@tanstack/react-router';
|
||||||
import { AnimatedOutletProvider } from '@/components/AnimatedOutlet';
|
|
||||||
import WindowFrame from '@/components/WindowFrame';
|
import WindowFrame from '@/components/WindowFrame';
|
||||||
|
import { AnimatedOutletProvider } from '@onelauncher/common/components';
|
||||||
import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router';
|
import { createRootRouteWithContext, Outlet, useRouter } from '@tanstack/react-router';
|
||||||
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
|
||||||
import { RouterProvider } from 'react-aria-components';
|
import { RouterProvider } from 'react-aria-components';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
import Button from '@/components/base/Button';
|
import { Button, TextField } from '@onelauncher/common/components';
|
||||||
import Dropdown from '@/components/base/Dropdown';
|
|
||||||
import { TextField } from '@/components/base/TextField';
|
|
||||||
import Modal from '@/components/overlay/Modal';
|
import Modal from '@/components/overlay/Modal';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,201 +1,199 @@
|
||||||
import useCommand from '@/hooks/useCommand'
|
import type { Model } from '@/bindings.gen';
|
||||||
import { bindings } from '@/main'
|
import type { Dispatch, SetStateAction } from 'react';
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import LoaderIcon from '@/components/launcher/LoaderIcon';
|
||||||
import Sidebar from '../settings/route'
|
import ScrollableContainer from '@/components/ScrollableContainer';
|
||||||
import Button from '@/components/base/Button'
|
import SettingsRow from '@/components/SettingsRow';
|
||||||
import { Edit02Icon, FolderIcon, ImagePlusIcon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-react'
|
import useCommand from '@/hooks/useCommand';
|
||||||
import LoaderIcon from '@/components/launcher/LoaderIcon'
|
import { bindings } from '@/main';
|
||||||
import { Show } from '@/components/base/Show'
|
import { Button, Show, TextField } from '@onelauncher/common/components';
|
||||||
import { TextField } from '@/components/base/TextField'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import type { Model } from '@/bindings.gen'
|
import { Edit02Icon, FolderIcon, ImagePlusIcon, Share07Icon, Tool02Icon, Trash01Icon } from '@untitled-theme/icons-react';
|
||||||
import ScrollableContainer from '@/components/ScrollableContainer'
|
import { useState } from 'react';
|
||||||
import { useState, type Dispatch, type SetStateAction } from 'react'
|
import Sidebar from '../settings/route';
|
||||||
import SettingsRow from '@/components/SettingsRow'
|
|
||||||
import ToggleButton from '@/components/base/ToggleButton'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/cluster/')({
|
export const Route = createFileRoute('/app/cluster/')({
|
||||||
component: RouteComponent
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { id } = Route.useSearch()
|
const { id } = Route.useSearch();
|
||||||
const [newCover, setNewCover] = useState<string>("")
|
const [newCover, setNewCover] = useState<string>('');
|
||||||
const [newName, setNewName] = useState<string>("")
|
const [newName, setNewName] = useState<string>('');
|
||||||
|
|
||||||
// dumbass fix ik
|
// dumbass fix ik
|
||||||
const cluster = useCommand("getClusterById", () => bindings.commands.getClusterById(Number(id.toString()) as unknown as bigint))
|
const cluster = useCommand('getClusterById', () => bindings.commands.getClusterById(Number(id.toString()) as unknown as bigint));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar.Page>
|
<Sidebar.Page>
|
||||||
<h1>Overview</h1>
|
<h1>Overview</h1>
|
||||||
<ScrollableContainer>
|
<ScrollableContainer>
|
||||||
<div className='h-full'>
|
<div className="h-full">
|
||||||
<Banner
|
<Banner
|
||||||
cluster={cluster.data}
|
cluster={cluster.data}
|
||||||
newCover={newCover}
|
editMode={false}
|
||||||
setNewCover={setNewCover}
|
newCover={newCover}
|
||||||
editMode={false}
|
newName={newName}
|
||||||
newName={newName}
|
refetch={cluster.refetch}
|
||||||
setNewName={setNewName}
|
setNewCover={setNewCover}
|
||||||
refetch={cluster.refetch}
|
setNewName={setNewName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingsRow.Header>Folders and Files</SettingsRow.Header>
|
<SettingsRow.Header>Folders and Files</SettingsRow.Header>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
children={(
|
children={(
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
children="Open"
|
||||||
children="Open"
|
color="primary"
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
description={"burada path var işte yersen"}
|
description="burada path var işte yersen"
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon={<FolderIcon />}
|
icon={<FolderIcon />}
|
||||||
title="Cluster Folder"
|
title="Cluster Folder"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SettingsRow.Header>Cluster Actions</SettingsRow.Header>
|
<SettingsRow.Header>Cluster Actions</SettingsRow.Header>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
children={(
|
children={(
|
||||||
<Button color='secondary'>Edit</Button>
|
<Button color="secondary">Edit</Button>
|
||||||
)}
|
)}
|
||||||
description="Edit the cluster name and cover image."
|
description="Edit the cluster name and cover image."
|
||||||
icon={<Edit02Icon />}
|
icon={<Edit02Icon />}
|
||||||
title="Edit Cluster"
|
title="Edit Cluster"
|
||||||
/>
|
/>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
children={(
|
children={(
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
children="Delete"
|
||||||
children="Delete"
|
color="danger"
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
description="Delete this cluster and all its data."
|
description="Delete this cluster and all its data."
|
||||||
disabled={false}
|
disabled={false}
|
||||||
icon={<Trash01Icon />}
|
icon={<Trash01Icon />}
|
||||||
title="Delete Cluster"
|
title="Delete Cluster"
|
||||||
/>
|
/>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
children={(
|
children={(
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
children="Repair"
|
||||||
children="Repair"
|
color="secondary"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
description="Verifies whether all assets, libraries and natives were properly installed."
|
description="Verifies whether all assets, libraries and natives were properly installed."
|
||||||
icon={<Tool02Icon />}
|
icon={<Tool02Icon />}
|
||||||
title="Verify Cluster"
|
title="Verify Cluster"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
</Sidebar.Page>
|
</Sidebar.Page>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BannerProps {
|
interface BannerProps {
|
||||||
cluster: Model | null | undefined;
|
cluster: Model | null | undefined;
|
||||||
editMode: boolean;
|
editMode: boolean;
|
||||||
newName: string;
|
newName: string;
|
||||||
setNewName: Dispatch<SetStateAction<string>>;
|
setNewName: Dispatch<SetStateAction<string>>;
|
||||||
newCover: string;
|
newCover: string;
|
||||||
setNewCover: Dispatch<SetStateAction<string>>;
|
setNewCover: Dispatch<SetStateAction<string>>;
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Banner(props: BannerProps) {
|
function Banner(props: BannerProps) {
|
||||||
// async function launchFilePicker() {
|
// async function launchFilePicker() {
|
||||||
// const selected = await dialog.open({
|
// const selected = await dialog.open({
|
||||||
// multiple: false,
|
// multiple: false,
|
||||||
// directory: false,
|
// directory: false,
|
||||||
// filters: [{
|
// filters: [{
|
||||||
// name: 'Image',
|
// name: 'Image',
|
||||||
// extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
// extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
// }],
|
// }],
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// if (selected === null)
|
// if (selected === null)
|
||||||
// return;
|
// return;
|
||||||
|
|
||||||
// props.setNewCover(selected);
|
// props.setNewCover(selected);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
function updateName(name: string) {
|
function updateName(name: string) {
|
||||||
if (name.length > 30 || name.length <= 0)
|
if (name.length > 30 || name.length <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
props.setNewName(name);
|
props.setNewName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-37 flex flex-row gap-x-2.5 rounded-xl bg-page-elevated p-2.5">
|
<div className="h-37 flex flex-row gap-x-2.5 rounded-xl bg-page-elevated p-2.5">
|
||||||
<div className="relative aspect-ratio-video h-full min-w-57 w-57 overflow-hidden border border-component-bg/10 rounded-lg">
|
<div className="relative aspect-ratio-video h-full min-w-57 w-57 overflow-hidden border border-component-bg/10 rounded-lg">
|
||||||
<Show when={props.editMode}>
|
<Show when={props.editMode}>
|
||||||
<div
|
<div
|
||||||
className="absolute z-1 h-full w-full flex items-center justify-center bg-black/50 opacity-50 hover:opacity-100"
|
className="absolute z-1 h-full w-full flex items-center justify-center bg-black/50 opacity-50 hover:opacity-100"
|
||||||
// onClick={launchFilePicker}
|
// onClick={launchFilePicker}
|
||||||
>
|
>
|
||||||
<ImagePlusIcon className="h-12 w-12" />
|
<ImagePlusIcon className="h-12 w-12" />
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
{/* <ClusterCover class="h-full w-full object-cover" cluster={props.cluster} override={props.newCover()} /> */}
|
{/* <ClusterCover class="h-full w-full object-cover" cluster={props.cluster} override={props.newCover()} /> */}
|
||||||
<img src='https://github.com/emirsassan.png' />
|
<img src="https://github.com/emirsassan.png" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex flex-col justify-between gap-y-.5 overflow-hidden text-fg-primary">
|
<div className="w-full flex flex-col justify-between gap-y-.5 overflow-hidden text-fg-primary">
|
||||||
<div>
|
<div>
|
||||||
<Show
|
<Show
|
||||||
fallback={
|
fallback={
|
||||||
<h2 className="break-words text-wrap text-2xl">{props.cluster?.name}</h2>
|
<h2 className="break-words text-wrap text-2xl">{props.cluster?.name}</h2>
|
||||||
}
|
}
|
||||||
when={props.editMode}
|
when={props.editMode}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
className="text-xl font-bold"
|
className="text-xl font-bold"
|
||||||
onChange={e => updateName(e.target.value)}
|
onChange={e => updateName(e.target.value)}
|
||||||
placeholder={props.cluster?.name}
|
placeholder={props.cluster?.name}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-row">
|
<div className="flex flex-1 flex-row">
|
||||||
<div
|
<div
|
||||||
className={"flex flex-1 flex-col items-start justify-between " + props.editMode ? "text-fg-primary-disabled" : ""}
|
className={`flex flex-1 flex-col items-start justify-between ${props.editMode}` ? 'text-fg-primary-disabled' : ''}
|
||||||
>
|
>
|
||||||
<span className="flex flex-row items-center gap-x-1">
|
<span className="flex flex-row items-center gap-x-1">
|
||||||
<LoaderIcon
|
<LoaderIcon
|
||||||
className="w-5"
|
className="w-5"
|
||||||
loader={props.cluster?.mc_loader}
|
loader={props.cluster?.mc_loader}
|
||||||
/>
|
/>
|
||||||
<span>{props.cluster?.mc_version}</span>
|
<span>{props.cluster?.mc_version}</span>
|
||||||
<span>{props.cluster?.mc_loader || 'unknown'}</span>
|
<span>{props.cluster?.mc_loader || 'unknown'}</span>
|
||||||
{props.cluster?.mc_loader_version && <span>{props.cluster.mc_loader_version}</span>}
|
{props.cluster?.mc_loader_version && <span>{props.cluster.mc_loader_version}</span>}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={"text-xs text-fg-secondary " + props.editMode ? "text-fg-secondary-disabled" : ""}
|
className={`text-xs text-fg-secondary ${props.editMode}` ? 'text-fg-secondary-disabled' : ''}
|
||||||
>
|
>
|
||||||
Played for
|
Played for
|
||||||
{' '}
|
{' '}
|
||||||
<b>{props.cluster?.overall_played || 0}</b>
|
<b>{props.cluster?.overall_played || 0}</b>
|
||||||
.
|
.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row items-end gap-x-2.5 *:h-8">
|
<div className="flex flex-row items-end gap-x-2.5 *:h-8">
|
||||||
<Button children="Launch" />
|
<Button children="Launch" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
children={<Share07Icon />}
|
||||||
children={<Share07Icon />}
|
color="secondary"
|
||||||
// disabled={props.editMode()}
|
// disabled={props.editMode()}
|
||||||
isDisabled={true}
|
isDisabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ import type { Model } from '@/bindings.gen';
|
||||||
import DefaultBanner from '@/assets/images/default_banner.png';
|
import DefaultBanner from '@/assets/images/default_banner.png';
|
||||||
import HeaderImage from '@/assets/images/default_banner.png';
|
import HeaderImage from '@/assets/images/default_banner.png';
|
||||||
import DefaultInstancePhoto from '@/assets/images/default_instance_cover.jpg';
|
import DefaultInstancePhoto from '@/assets/images/default_instance_cover.jpg';
|
||||||
import Button from '@/components/base/Button';
|
|
||||||
import { TextField } from '@/components/base/TextField';
|
|
||||||
import Modal from '@/components/overlay/Modal';
|
import Modal from '@/components/overlay/Modal';
|
||||||
import useCommand from '@/hooks/useCommand';
|
|
||||||
import { bindings } from '@/main';
|
import { bindings } from '@/main';
|
||||||
|
import { useCommand } from '@onelauncher/common';
|
||||||
|
import { Button, TextField } from '@onelauncher/common/components';
|
||||||
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
|
||||||
import { Server01Icon } from '@untitled-theme/icons-react';
|
import { Server01Icon } from '@untitled-theme/icons-react';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
|
@ -21,10 +20,6 @@ and i am very angry rn so i will not be fixing it rn
|
||||||
*/
|
*/
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const result = useCommand('getClusters', bindings.core.get_clusters);
|
const result = useCommand('getClusters', bindings.core.get_clusters);
|
||||||
const test = useCommand('ajgiagwo', bindings.onelauncher.return_error, {
|
|
||||||
enabled: true,
|
|
||||||
retry: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col gap-y-4 text-fg-primary">
|
<div className="h-full flex flex-col gap-y-4 text-fg-primary">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { AnimatedOutlet } from '@/components/AnimatedOutlet';
|
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
|
import { AnimatedOutlet } from '@onelauncher/common/components';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,140 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import PlayerHead from '@/components/content/PlayerHead';
|
||||||
import Sidebar from './route'
|
import ScrollableContainer from '@/components/ScrollableContainer';
|
||||||
import ScrollableContainer from '@/components/ScrollableContainer'
|
import useCommand from '@/hooks/useCommand';
|
||||||
import Button from '@/components/base/Button'
|
import { bindings } from '@/main';
|
||||||
import { InfoCircleIcon, Trash01Icon, UserPlus02Icon } from '@untitled-theme/icons-react'
|
import { Button, Show, Tooltip } from '@onelauncher/common/components';
|
||||||
import PlayerHead from '@/components/content/PlayerHead'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { useState } from 'react'
|
import { InfoCircleIcon, Trash01Icon, UserPlus02Icon } from '@untitled-theme/icons-react';
|
||||||
import { Show } from '@/components/base/Show'
|
import { useState } from 'react';
|
||||||
import Tooltip from '@/components/base/Tooltip'
|
import Sidebar from './route';
|
||||||
import useCommand from '@/hooks/useCommand'
|
|
||||||
import { bindings } from '@/main'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/settings/accounts')({
|
export const Route = createFileRoute('/app/settings/accounts')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
interface Account {
|
interface Account {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { data: usersData, isLoading: usersLoading } = useCommand("getUsers", bindings.commands.getUsers)
|
const { data: usersData, isLoading: usersLoading } = useCommand('getUsers', bindings.commands.getUsers);
|
||||||
const { data: defaultUserData, isLoading: defaultUserLoading } = useCommand("getDefaultUser", () => bindings.commands.getDefaultUser(false))
|
const { data: defaultUserData, isLoading: defaultUserLoading } = useCommand('getDefaultUser', () => bindings.commands.getDefaultUser(false));
|
||||||
|
|
||||||
if (usersLoading || defaultUserLoading) {
|
if (usersLoading || defaultUserLoading)
|
||||||
return <Sidebar.Page><h1>Accounts</h1><p>Loading accounts...</p></Sidebar.Page>;
|
return (
|
||||||
}
|
<Sidebar.Page>
|
||||||
|
<h1>Accounts</h1>
|
||||||
|
<p>Loading accounts...</p>
|
||||||
|
</Sidebar.Page>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar.Page>
|
<Sidebar.Page>
|
||||||
<h1>Accounts</h1>
|
<h1>Accounts</h1>
|
||||||
<ScrollableContainer>
|
<ScrollableContainer>
|
||||||
<div className="h-full space-y-2">
|
<div className="h-full space-y-2">
|
||||||
<Show when={usersData && usersData.length > 0} fallback={<p>No accounts found.</p>}>
|
<Show fallback={<p>No accounts found.</p>} when={usersData && usersData.length > 0}>
|
||||||
{usersData?.map((account) => (
|
{usersData?.map(account => (
|
||||||
<AccountRow
|
<AccountRow
|
||||||
key={account.id}
|
account={account}
|
||||||
account={account}
|
isCurrent={account.id === defaultUserData?.id}
|
||||||
isCurrent={account.id === defaultUserData?.id}
|
key={account.id}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
|
|
||||||
<div className="mt-auto pt-2 flex flex-row items-end justify-end">
|
<div className="mt-auto pt-2 flex flex-row items-end justify-end">
|
||||||
<Button color="primary">
|
<Button color="primary">
|
||||||
<UserPlus02Icon /> Add Account
|
<UserPlus02Icon />
|
||||||
</Button>
|
{' '}
|
||||||
</div>
|
Add Account
|
||||||
</Sidebar.Page>
|
</Button>
|
||||||
)
|
</div>
|
||||||
|
</Sidebar.Page>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AccountRowProps {
|
interface AccountRowProps {
|
||||||
account: Account;
|
account: Account;
|
||||||
isCurrent?: boolean;
|
isCurrent?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function AccountRow({ account, isCurrent }: AccountRowProps) {
|
function AccountRow({ account, isCurrent }: AccountRowProps) {
|
||||||
const [errored, setErrored] = useState(false);
|
const [errored, setErrored] = useState(false);
|
||||||
|
|
||||||
const setDefaultUserCommand = useCommand(
|
const setDefaultUserCommand = useCommand(
|
||||||
"setDefaultUser",
|
'setDefaultUser',
|
||||||
() => bindings.commands.setDefaultUser(account.id),
|
() => bindings.commands.setDefaultUser(account.id),
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
subscribed: false,
|
subscribed: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeUserCommand = useCommand(
|
const removeUserCommand = useCommand(
|
||||||
"removeUser",
|
'removeUser',
|
||||||
() => bindings.commands.removeUser(account.id),
|
() => bindings.commands.removeUser(account.id),
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
subscribed: false,
|
subscribed: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSetDefault = () => {
|
const handleSetDefault = () => {
|
||||||
setDefaultUserCommand.refetch();
|
setDefaultUserCommand.refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (e: React.MouseEvent) => {
|
const handleDelete = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
removeUserCommand.refetch();
|
removeUserCommand.refetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-row bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed rounded-xl gap-3.5 p-4 items-center box-border border ${isCurrent ? 'border-brand' : 'border-transparent'} cursor-pointer`} // Added cursor-pointer
|
className={`flex flex-row bg-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed rounded-xl gap-3.5 p-4 items-center box-border border ${isCurrent ? 'border-brand' : 'border-transparent'} cursor-pointer`} // Added cursor-pointer
|
||||||
onClick={handleSetDefault}
|
onClick={handleSetDefault}
|
||||||
role="button"
|
onKeyDown={(e) => {
|
||||||
tabIndex={0}
|
if (e.key === 'Enter' || e.key === ' ')
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleSetDefault(); }}
|
handleSetDefault();
|
||||||
>
|
}}
|
||||||
<div className="h-12 w-12 flex items-center justify-center flex-shrink-0">
|
role="button"
|
||||||
<PlayerHead
|
tabIndex={0}
|
||||||
className="h-12 w-12 rounded-md"
|
>
|
||||||
onError={() => setErrored(true)}
|
<div className="h-12 w-12 flex items-center justify-center flex-shrink-0">
|
||||||
uuid={account.id}
|
<PlayerHead
|
||||||
/>
|
className="h-12 w-12 rounded-md"
|
||||||
</div>
|
onError={() => setErrored(true)}
|
||||||
|
uuid={account.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={`flex flex-col gap-1 flex-1 min-w-0 ${errored ? 'text-danger' : ''}`}>
|
<div className={`flex flex-col gap-1 flex-1 min-w-0 ${errored ? 'text-danger' : ''}`}>
|
||||||
<div className="flex flex-row items-center gap-1">
|
<div className="flex flex-row items-center gap-1">
|
||||||
<h3 className="text-lg font-medium truncate">{account.username}</h3>
|
<h3 className="text-lg font-medium truncate">{account.username}</h3>
|
||||||
<Show when={errored}>
|
<Show when={errored}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
text="Could not fetch this account's game profile"
|
text="Could not fetch this account's game profile"
|
||||||
>
|
>
|
||||||
<InfoCircleIcon className="h-4 w-4 flex-shrink-0" />
|
<InfoCircleIcon className="h-4 w-4 flex-shrink-0" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-fg-secondary truncate">{account.id}</p>
|
<p className="text-xs text-fg-secondary truncate">{account.id}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="ml-auto flex-shrink-0">
|
<div className="ml-auto flex-shrink-0">
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
aria-label={`Remove account ${account.username}`}
|
||||||
size='icon'
|
color="danger"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
aria-label={`Remove account ${account.username}`}
|
size="icon"
|
||||||
>
|
>
|
||||||
<Trash01Icon />
|
<Trash01Icon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,26 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import ScrollableContainer from '@/components/ScrollableContainer';
|
||||||
import Sidebar from './route'
|
import SettingsRow from '@/components/SettingsRow';
|
||||||
import ScrollableContainer from '@/components/ScrollableContainer'
|
import { THEMES } from '@/utils/theming';
|
||||||
import { THEMES } from '@/utils/theming'
|
import { Switch } from '@onelauncher/common/components';
|
||||||
import SettingsRow from '@/components/SettingsRow'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { Monitor01Icon, PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-react'
|
import { Monitor01Icon, PackageIcon, Speedometer04Icon } from '@untitled-theme/icons-react';
|
||||||
import ToggleButton from '@/components/base/ToggleButton'
|
import Sidebar from './route';
|
||||||
import Switch from '@/components/base/Switch'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/settings/appearance')({
|
export const Route = createFileRoute('/app/settings/appearance')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<Sidebar.Page>
|
<Sidebar.Page>
|
||||||
<ScrollableContainer>
|
<ScrollableContainer>
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<h1>Appearance</h1>
|
<h1>Appearance</h1>
|
||||||
|
|
||||||
<div className="flex flex-row items-center gap-4">
|
<div className="flex flex-row items-center gap-4">
|
||||||
<PrimaryThemeCard />
|
<PrimaryThemeCard />
|
||||||
<div className="grid grid-cols-3 h-full gap-4">
|
<div className="grid grid-cols-3 h-full gap-4">
|
||||||
{/* <For each={Object.keys(THEMES) as (keyof typeof THEMES)[]}>
|
{/* <For each={Object.keys(THEMES) as (keyof typeof THEMES)[]}>
|
||||||
{theme => (
|
{theme => (
|
||||||
<For each={THEMES[theme ?? 'OneLauncher'].variants}>
|
<For each={THEMES[theme ?? 'OneLauncher'].variants}>
|
||||||
{variant => (
|
{variant => (
|
||||||
|
|
@ -34,82 +33,184 @@ function RouteComponent() {
|
||||||
)}
|
)}
|
||||||
</For> */}
|
</For> */}
|
||||||
|
|
||||||
{Object.keys(THEMES).map((theme) => {
|
{Object.keys(THEMES).map((theme) => {
|
||||||
return THEMES[theme as keyof typeof THEMES].variants.map((variant, i: number) => {
|
return THEMES[theme as keyof typeof THEMES].variants.map((variant) => {
|
||||||
return (
|
return (
|
||||||
<ThemeCard themeSelector={`${theme}-${variant.name}`} setTheme={() => { }} key={i} />
|
<ThemeCard key={`${theme}-${variant.name}`} setTheme={() => { }} themeSelector={`${theme}-${variant.name}`} />
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Change the look of the package list."
|
description="Change the look of the package list."
|
||||||
icon={<PackageIcon />}
|
icon={<PackageIcon />}
|
||||||
title="Package List Style"
|
title="Package List Style"
|
||||||
>
|
>
|
||||||
<p>i hope i'll fix this</p>
|
<p>i hope i'll fix this</p>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Uses custom window frame for the launcher."
|
description="Uses custom window frame for the launcher."
|
||||||
icon={<Monitor01Icon />}
|
icon={<Monitor01Icon />}
|
||||||
title="Custom Window Frame"
|
title="Custom Window Frame"
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Disables all animations in the launcher."
|
description="Disables all animations in the launcher."
|
||||||
icon={<Speedometer04Icon />}
|
icon={<Speedometer04Icon />}
|
||||||
title="Disable Animations"
|
title="Disable Animations"
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</div>
|
</div>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
</Sidebar.Page>
|
</Sidebar.Page>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ThemeCardProps {
|
interface ThemeCardProps {
|
||||||
themeSelector: string;
|
themeSelector: string;
|
||||||
setTheme: (merged: string) => void;
|
setTheme: (merged: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ThemeCard(props: ThemeCardProps) {
|
function ThemeCard(props: ThemeCardProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`h-min theme-${props.themeSelector}`} onClick={() => props.setTheme(props.themeSelector)}>
|
<div className={`h-min theme-${props.themeSelector}`} onClick={() => props.setTheme(props.themeSelector)}>
|
||||||
<svg fill="none" height="78" viewBox="0 0 126 78" width="126" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
<rect className='fill-page-elevated' height="78" rx="8" width="126" />
|
fill="none"
|
||||||
<rect height="77" rx="7.5" className='stroke-border' stroke-opacity="0.1" width="125" x="0.5" y="0.5" />
|
height="78"
|
||||||
<path d="M8 16H116" className='stroke-fg-primary' stroke-linecap="round" stroke-width="5" />
|
viewBox="0 0 126 78"
|
||||||
<path d="M8 26H56" className="stroke-fg-secondary" stroke-linecap="round" stroke-width="3" />
|
width="126"
|
||||||
<rect className="fill-brand" fill-opacity="0.5" height="16" rx="4" width="64" x="54" y="54" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path d="M54 58C54 55.7909 55.7909 54 58 54H86V70H58C55.7909 70 54 68.2091 54 66V58Z" fill="rgb(var(--clr-brand))" />
|
>
|
||||||
</svg>
|
<rect
|
||||||
|
className="fill-page-elevated"
|
||||||
|
height="78"
|
||||||
|
rx="8"
|
||||||
|
width="126"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className="stroke-border"
|
||||||
|
height="77"
|
||||||
|
rx="7.5"
|
||||||
|
stroke-opacity="0.1"
|
||||||
|
width="125"
|
||||||
|
x="0.5"
|
||||||
|
y="0.5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-fg-primary"
|
||||||
|
d="M8 16H116"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-fg-secondary"
|
||||||
|
d="M8 26H56"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="3"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className="fill-brand"
|
||||||
|
fill-opacity="0.5"
|
||||||
|
height="16"
|
||||||
|
rx="4"
|
||||||
|
width="64"
|
||||||
|
x="54"
|
||||||
|
y="54"
|
||||||
|
/>
|
||||||
|
<path d="M54 58C54 55.7909 55.7909 54 58 54H86V70H58C55.7909 70 54 68.2091 54 66V58Z" fill="rgb(var(--clr-brand))" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PrimaryThemeCard() {
|
function PrimaryThemeCard() {
|
||||||
return (
|
return (
|
||||||
<div className='w-[296] h-[183]'>
|
<div className="w-[296] h-[183]">
|
||||||
<svg fill="none" height="183" viewBox="0 0 296 183" width="296" xmlns="http://www.w3.org/2000/svg">
|
<svg
|
||||||
<rect className="fill-page" height="183" rx="16" width="296" />
|
fill="none"
|
||||||
<rect className="stroke-border/10" height="180" rx="14.5" strokeWidth="1.5" width="293" x="1.5" y="1.5" />
|
height="183"
|
||||||
<rect className="fill-page-elevated" height="123" rx="8" width="168" x="112" y="44" />
|
viewBox="0 0 296 183"
|
||||||
<rect className="stroke-border/5" height="122" rx="7.5" width="167" x="112.5" y="44.5" />
|
width="296"
|
||||||
<rect className="fill-brand/50" height="16" rx="4" width="64" x="208" y="143" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path className="stroke-fg-primary" d="M132 64H240" strokeLinecap="round" strokeWidth="5" />
|
>
|
||||||
<path className="stroke-fg-secondary" d="M132 74H180" strokeLinecap="round" strokeWidth="3" />
|
<rect
|
||||||
<path className="stroke-code-error" d="M132 94H216" strokeLinecap="round" strokeWidth="4" />
|
className="fill-page"
|
||||||
<path className="stroke-code-warn" d="M132 104H192" strokeLinecap="round" strokeWidth="4" />
|
height="183"
|
||||||
<path className="stroke-code-info" d="M132 114H210" strokeLinecap="round" strokeWidth="4" />
|
rx="16"
|
||||||
<path className="fill-brand" d="M208 147C208 144.791 209.791 143 212 143H240V159H212C209.791 159 208 157.209 208 155V147Z" />
|
width="296"
|
||||||
</svg>
|
/>
|
||||||
</div>
|
<rect
|
||||||
);
|
className="stroke-border/10"
|
||||||
}
|
height="180"
|
||||||
|
rx="14.5"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
width="293"
|
||||||
|
x="1.5"
|
||||||
|
y="1.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className="fill-page-elevated"
|
||||||
|
height="123"
|
||||||
|
rx="8"
|
||||||
|
width="168"
|
||||||
|
x="112"
|
||||||
|
y="44"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className="stroke-border/5"
|
||||||
|
height="122"
|
||||||
|
rx="7.5"
|
||||||
|
width="167"
|
||||||
|
x="112.5"
|
||||||
|
y="44.5"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
className="fill-brand/50"
|
||||||
|
height="16"
|
||||||
|
rx="4"
|
||||||
|
width="64"
|
||||||
|
x="208"
|
||||||
|
y="143"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-fg-primary"
|
||||||
|
d="M132 64H240"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="5"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-fg-secondary"
|
||||||
|
d="M132 74H180"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="3"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-code-error"
|
||||||
|
d="M132 94H216"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-code-warn"
|
||||||
|
d="M132 104H192"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
className="stroke-code-info"
|
||||||
|
d="M132 114H210"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeWidth="4"
|
||||||
|
/>
|
||||||
|
<path className="fill-brand" d="M208 147C208 144.791 209.791 143 212 143H240V159H212C209.791 159 208 157.209 208 155V147Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,41 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import DiscordIcon from '@/assets/logos/discord.svg';
|
||||||
import Sidebar from './route'
|
import SettingsRow from '@/components/SettingsRow';
|
||||||
import useCommand from '@/hooks/useCommand'
|
import { bindings } from '@/main';
|
||||||
import { bindings } from '@/main'
|
import { useCommand } from '@onelauncher/common';
|
||||||
import SettingsRow from '@/components/SettingsRow'
|
import { Button, Switch } from '@onelauncher/common/components';
|
||||||
import DiscordIcon from "@/assets/logos/discord.svg"
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import Switch from '@/components/base/Switch'
|
import { FolderIcon, LinkExternal01Icon, XIcon } from '@untitled-theme/icons-react';
|
||||||
import Button from '@/components/base/Button'
|
import Sidebar from './route';
|
||||||
import { FolderIcon, LinkExternal01Icon, XIcon } from '@untitled-theme/icons-react'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/settings/')({
|
export const Route = createFileRoute('/app/settings/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const result = useCommand("getGlobalProfile", bindings.commands.getGlobalProfile)
|
const result = useCommand('getGlobalProfile', bindings.commands.getGlobalProfile);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar.Page>
|
<Sidebar.Page>
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<h1>General Settings</h1>
|
<h1>General Settings</h1>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Enable Discord Rich Presence."
|
description="Enable Discord Rich Presence."
|
||||||
icon={<img src={DiscordIcon} className="w-6 invert-100" />}
|
icon={<img className="w-6 invert-100" src={DiscordIcon} />}
|
||||||
title="Discord RPC"
|
title="Discord RPC"
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Hide the confirmation dialog when closing the launcher."
|
description="Hide the confirmation dialog when closing the launcher."
|
||||||
icon={<XIcon />}
|
icon={<XIcon />}
|
||||||
title="Hide Close Dialog"
|
title="Hide Close Dialog"
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
{/* <SettingsRow
|
{/* <SettingsRow
|
||||||
description="Sends errors and crash logs using Sentry to help developers fix issues. (// TODO)"
|
description="Sends errors and crash logs using Sentry to help developers fix issues. (// TODO)"
|
||||||
icon={<AlertSquareIcon />}
|
icon={<AlertSquareIcon />}
|
||||||
title="Error Analytics"
|
title="Error Analytics"
|
||||||
|
|
@ -47,17 +46,19 @@ function RouteComponent() {
|
||||||
/>
|
/>
|
||||||
</SettingsRow> */}
|
</SettingsRow> */}
|
||||||
|
|
||||||
<SettingsRow.Header>Folders and Files</SettingsRow.Header>
|
<SettingsRow.Header>Folders and Files</SettingsRow.Header>
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description={'Unknown for now'}
|
description="Unknown for now"
|
||||||
icon={<FolderIcon />}
|
icon={<FolderIcon />}
|
||||||
title="Launcher Folder"
|
title="Launcher Folder"
|
||||||
>
|
>
|
||||||
<Button size='normal'>
|
<Button size="normal">
|
||||||
<LinkExternal01Icon /> Open
|
<LinkExternal01Icon />
|
||||||
</Button>
|
{' '}
|
||||||
</SettingsRow>
|
Open
|
||||||
</div>
|
</Button>
|
||||||
</Sidebar.Page>
|
</SettingsRow>
|
||||||
)
|
</div>
|
||||||
|
</Sidebar.Page>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,142 +1,143 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import ScrollableContainer from '@/components/ScrollableContainer';
|
||||||
import Sidebar from './route'
|
import SettingsRow from '@/components/SettingsRow';
|
||||||
import SettingsRow from '@/components/SettingsRow'
|
import { Switch, TextField, ToggleButton } from '@onelauncher/common/components';
|
||||||
import { Database01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, XIcon } from '@untitled-theme/icons-react'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import Button from '@/components/base/Button'
|
import { Database01Icon, EyeIcon, FilePlus02Icon, FileX02Icon, LayoutTopIcon, Maximize01Icon, ParagraphWrapIcon, XIcon } from '@untitled-theme/icons-react';
|
||||||
import useCommand from '@/hooks/useCommand'
|
import Sidebar from './route';
|
||||||
import { bindings } from '@/main'
|
|
||||||
import { TextField } from '@/components/base/TextField'
|
|
||||||
import ToggleButton from '@/components/base/ToggleButton'
|
|
||||||
import ScrollableContainer from '@/components/ScrollableContainer'
|
|
||||||
import Switch from '@/components/base/Switch'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/settings/minecraft')({
|
export const Route = createFileRoute('/app/settings/minecraft')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<Sidebar.Page>
|
<Sidebar.Page>
|
||||||
<ScrollableContainer>
|
<ScrollableContainer>
|
||||||
<div className="h-full">
|
<div className="h-full">
|
||||||
<h1>Minecraft Settings</h1>
|
<h1>Minecraft Settings</h1>
|
||||||
|
|
||||||
<GameSettings />
|
<GameSettings />
|
||||||
|
|
||||||
{/* <LauncherSettings /> */}
|
{/* <LauncherSettings /> */}
|
||||||
|
|
||||||
<ProcessSettings />
|
<ProcessSettings />
|
||||||
</div>
|
</div>
|
||||||
</ScrollableContainer>
|
</ScrollableContainer>
|
||||||
</Sidebar.Page>
|
</Sidebar.Page>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GameSettings() {
|
export function GameSettings() {
|
||||||
return <>
|
return (
|
||||||
<SettingsRow.Header>Game</SettingsRow.Header>
|
<>
|
||||||
|
<SettingsRow.Header>Game</SettingsRow.Header>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Force Minecraft to start in fullscreen mode."
|
description="Force Minecraft to start in fullscreen mode."
|
||||||
icon={<Maximize01Icon />}
|
icon={<Maximize01Icon />}
|
||||||
title="Force Fullscreen"
|
title="Force Fullscreen"
|
||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="The game window resolution in pixels."
|
description="The game window resolution in pixels."
|
||||||
icon={<LayoutTopIcon />}
|
icon={<LayoutTopIcon />}
|
||||||
title="Resolution"
|
title="Resolution"
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-[70px_16px_70px] gap-2 grid-justify-center grid-items-center">
|
<div className="grid grid-cols-[70px_16px_70px] gap-2 grid-justify-center grid-items-center">
|
||||||
<TextField
|
<TextField
|
||||||
type='number'
|
className="text-center"
|
||||||
className="text-center"
|
type="number"
|
||||||
/>
|
/>
|
||||||
<XIcon className="size-4 self-center" />
|
<XIcon className="size-4 self-center" />
|
||||||
<TextField
|
<TextField
|
||||||
type='number'
|
className="text-center"
|
||||||
className="text-center"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="The amount of memory in megabytes allocated for the game."
|
description="The amount of memory in megabytes allocated for the game."
|
||||||
icon={<Database01Icon />}
|
icon={<Database01Icon />}
|
||||||
title="Memory"
|
title="Memory"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-x-4 flex-justify-center">
|
<div className="flex items-center gap-x-4 flex-justify-center">
|
||||||
<div className="flex flex-row items-center gap-x-2">
|
<div className="flex flex-row items-center gap-x-2">
|
||||||
<span>Min:</span>
|
<span>Min:</span>
|
||||||
<TextField
|
<TextField
|
||||||
className="text-center"
|
className="text-center"
|
||||||
type='number'
|
max={2024}
|
||||||
max={2024}
|
min={1}
|
||||||
min={1}
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-row items-center gap-x-2">
|
<div className="flex flex-row items-center gap-x-2">
|
||||||
<span>Max:</span>
|
<span>Max:</span>
|
||||||
<TextField
|
<TextField
|
||||||
className="text-center"
|
className="text-center"
|
||||||
type='number'
|
max={2024}
|
||||||
max={2024}
|
min={1}
|
||||||
min={1}
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</>
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function LauncherSettings() {
|
// function LauncherSettings() {
|
||||||
return <>
|
// return (
|
||||||
<SettingsRow.Header>Launcher</SettingsRow.Header>
|
// <>
|
||||||
<SettingsRow
|
// <SettingsRow.Header>Launcher</SettingsRow.Header>
|
||||||
description="Hide the launcher whenever you start a game."
|
// <SettingsRow
|
||||||
icon={<EyeIcon />}
|
// description="Hide the launcher whenever you start a game."
|
||||||
title="Hide On Launch"
|
// icon={<EyeIcon />}
|
||||||
>
|
// title="Hide On Launch"
|
||||||
<ToggleButton color='primary' children="false" />
|
// >
|
||||||
</SettingsRow>
|
// <ToggleButton children="false" color="primary" />
|
||||||
</>
|
// </SettingsRow>
|
||||||
}
|
// </>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
export function ProcessSettings() {
|
export function ProcessSettings() {
|
||||||
return <>
|
return (
|
||||||
<SettingsRow.Header>Process</SettingsRow.Header>
|
<>
|
||||||
|
<SettingsRow.Header>Process</SettingsRow.Header>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Command to run before launching the game."
|
description="Command to run before launching the game."
|
||||||
icon={<FilePlus02Icon />}
|
icon={<FilePlus02Icon />}
|
||||||
title="Pre-Launch Command"
|
title="Pre-Launch Command"
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="echo 'Game started'"
|
placeholder="echo 'Game started'"
|
||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Command to run when launching the game."
|
description="Command to run when launching the game."
|
||||||
icon={<ParagraphWrapIcon />}
|
icon={<ParagraphWrapIcon />}
|
||||||
title="Wrapper Command"
|
title="Wrapper Command"
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="gamescope"
|
placeholder="gamescope"
|
||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
|
|
||||||
<SettingsRow
|
<SettingsRow
|
||||||
description="Command to run after exiting the game."
|
description="Command to run after exiting the game."
|
||||||
icon={<FileX02Icon />}
|
icon={<FileX02Icon />}
|
||||||
title="Post-Exit Command"
|
title="Post-Exit Command"
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="echo 'Game exited'"
|
placeholder="echo 'Game exited'"
|
||||||
/>
|
/>
|
||||||
</SettingsRow>
|
</SettingsRow>
|
||||||
</>
|
</>
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,122 @@
|
||||||
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
|
import type { JSX } from 'react';
|
||||||
|
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router';
|
||||||
import { Brush01Icon, CodeSnippet02Icon, MessageTextSquare01Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon, Users01Icon } from '@untitled-theme/icons-react';
|
import { Brush01Icon, CodeSnippet02Icon, MessageTextSquare01Icon, RefreshCcw02Icon, Rocket02Icon, Sliders04Icon, Users01Icon } from '@untitled-theme/icons-react';
|
||||||
import { useEffect, type JSX } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export const Route = createFileRoute('/app/settings')({
|
export const Route = createFileRoute('/app/settings')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-1 flex-row gap-x-7">
|
<div className="h-full flex flex-1 flex-row gap-x-7">
|
||||||
<div className="mt-8 flex flex-col justify-between">
|
<div className="mt-8 flex flex-col justify-between">
|
||||||
<Sidebar
|
<Sidebar
|
||||||
base="/app/settings"
|
base="/app/settings"
|
||||||
links={{
|
links={{
|
||||||
'Launcher Settings': [
|
'Launcher Settings': [
|
||||||
[<Rocket02Icon />, 'General', '/'],
|
[<Rocket02Icon />, 'General', '/'],
|
||||||
[<Brush01Icon />, 'Appearance', '/appearance'],
|
[<Brush01Icon />, 'Appearance', '/appearance'],
|
||||||
// [<Key01Icon />, 'APIs', '/apis'],
|
// [<Key01Icon />, 'APIs', '/apis'],
|
||||||
// [<Globe01Icon />, 'Language', '/language'],
|
// [<Globe01Icon />, 'Language', '/language'],
|
||||||
],
|
],
|
||||||
'Game Settings': [
|
'Game Settings': [
|
||||||
[<Sliders04Icon />, 'Minecraft settings', '/minecraft'],
|
[<Sliders04Icon />, 'Minecraft settings', '/minecraft'],
|
||||||
[<Users01Icon />, 'Accounts', '/accounts'],
|
[<Users01Icon />, 'Accounts', '/accounts'],
|
||||||
],
|
],
|
||||||
'About': [
|
'About': [
|
||||||
[<RefreshCcw02Icon />, 'Changelog', '/changelog'],
|
[<RefreshCcw02Icon />, 'Changelog', '/changelog'],
|
||||||
[<MessageTextSquare01Icon />, 'Feedback', '/feedback'],
|
[<MessageTextSquare01Icon />, 'Feedback', '/feedback'],
|
||||||
[<CodeSnippet02Icon />, 'Developer Options', '/developer'],
|
[<CodeSnippet02Icon />, 'Developer Options', '/developer'],
|
||||||
],
|
],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/* <Info /> */}
|
{/* <Info /> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-full w-full flex flex-col">
|
<div className="h-full w-full flex flex-col">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type SidebarProps = {
|
interface SidebarProps {
|
||||||
base: string;
|
base: string;
|
||||||
links: Record<string, ([JSX.Element, string, string, URLSearchParams?] | undefined)[]>;
|
links: Record<string, Array<[JSX.Element, string, string, URLSearchParams?] | undefined>>;
|
||||||
};
|
}
|
||||||
|
|
||||||
function Sidebar(props: SidebarProps) {
|
function Sidebar(props: SidebarProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const routerState = useRouterState();
|
const routerState = useRouterState();
|
||||||
|
|
||||||
const location = routerState.location.pathname
|
const location = routerState.location.pathname;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.base.endsWith('/'))
|
if (props.base.endsWith('/'))
|
||||||
throw new Error('Base should not end with a slash');
|
throw new Error('Base should not end with a slash');
|
||||||
});
|
});
|
||||||
|
|
||||||
function goto(href: string, params?: URLSearchParams) {
|
function goto(href: string, params?: URLSearchParams) {
|
||||||
const currParams = new URLSearchParams(location);
|
const currParams = new URLSearchParams(location);
|
||||||
if (params)
|
if (params)
|
||||||
for (const [key, value] of params)
|
for (const [key, value] of params)
|
||||||
currParams.set(key, value);
|
currParams.set(key, value);
|
||||||
|
|
||||||
const url = `${props.base}${href}`;
|
const url = `${props.base}${href}`;
|
||||||
navigate({ to: url })
|
navigate({ to: url });
|
||||||
}
|
}
|
||||||
|
|
||||||
function isActive(link: string, _params: URLSearchParams | undefined) {
|
function isActive(link: string, _params: URLSearchParams | undefined) {
|
||||||
return location === `${props.base}${link}` || `${location}/` === `${props.base}${link}`;
|
return location === `${props.base}${link}` || `${location}/` === `${props.base}${link}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-56 flex flex-col pr-2">
|
<div className="w-56 flex flex-col pr-2">
|
||||||
{Object.keys(props.links).map((section, i) => (
|
{Object.keys(props.links).map((section, i) => (
|
||||||
<div className="flex flex-col gap-y-2" key={i}>
|
<div className="flex flex-col gap-y-2" key={i}>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="m-1.5 mt-5 text-xs text-fg-secondary font-medium">{section.toUpperCase()}</h3>
|
<h3 className="m-1.5 mt-5 text-xs text-fg-secondary font-medium">{section.toUpperCase()}</h3>
|
||||||
<div className="flex flex-col gap-y-1 fill-fg-primary text-fg-primary font-medium">
|
<div className="flex flex-col gap-y-1 fill-fg-primary text-fg-primary font-medium">
|
||||||
{props.links[section].map((link, i) => {
|
{props.links[section].map((link, i) => {
|
||||||
if (!link)
|
if (!link)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className={
|
className={
|
||||||
`px-3 py-1 rounded-md text-md border border-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link[2], link[3]) ? 'bg-component-bg border-border/05' : 'border-transparent'}`
|
`px-3 py-1 rounded-md text-md border border-component-bg hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link[2], link[3]) ? 'bg-component-bg border-border/05' : 'border-transparent'}`
|
||||||
}
|
}
|
||||||
key={i}
|
key={i}
|
||||||
onClick={() => goto(link[2], link[3])}
|
onClick={() => goto(link[2], link[3])}
|
||||||
>
|
>
|
||||||
<span className="flex flex-row items-center gap-x-3 *:w-5">
|
<span className="flex flex-row items-center gap-x-3 *:w-5">
|
||||||
{link[0]}
|
{link[0]}
|
||||||
{link[1]}
|
{link[1]}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SidebarPageProps {
|
interface SidebarPageProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
children: JSX.Element | JSX.Element[];
|
children: JSX.Element | Array<JSX.Element>;
|
||||||
}
|
}
|
||||||
|
|
||||||
Sidebar.Page = function (props: SidebarPageProps) {
|
Sidebar.Page = function (props: SidebarPageProps) {
|
||||||
return (
|
return (
|
||||||
<div {...props} className={`flex flex-col flex-1 gap-y-1 overflow-y-auto h-full ${props.className || ''}`}>
|
<div {...props} className={`flex flex-col flex-1 gap-y-1 overflow-y-auto h-full ${props.className || ''}`}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
export default Sidebar;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,42 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import Illustration from '@/assets/illustrations/onboarding/import_from_others.svg';
|
||||||
import { OnboardingStep } from './route'
|
|
||||||
import Illustration from '../../assets/illustrations/onboarding/import_from_others.svg';
|
|
||||||
import { LAUNCHER_IMPORT_TYPES } from '@/utils';
|
|
||||||
import LauncherIcon from '@/components/content/LauncherIcon';
|
import LauncherIcon from '@/components/content/LauncherIcon';
|
||||||
|
import { LAUNCHER_IMPORT_TYPES } from '@/utils';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { OnboardingStep } from './route';
|
||||||
|
|
||||||
export const Route = createFileRoute('/onboarding/import')({
|
export const Route = createFileRoute('/onboarding/import')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OnboardingStep illustration={<img src={Illustration} />} paragraph="Import your profiles from other launchers."
|
<OnboardingStep
|
||||||
title="Import">
|
illustration={<img src={Illustration} />}
|
||||||
<div className="h-full w-full flex flex-col gap-y-3">
|
paragraph="Import your profiles from other launchers."
|
||||||
<div className="grid grid-cols-3">
|
title="Import"
|
||||||
{LAUNCHER_IMPORT_TYPES.map((x) => (
|
>
|
||||||
<button
|
<div className="h-full w-full flex flex-col gap-y-3">
|
||||||
className={`flex flex-col items-center justify-center gap-y-4 rounded-md p-4 active:bg-border/10 hover:bg-border/05`}
|
<div className="grid grid-cols-3">
|
||||||
>
|
{LAUNCHER_IMPORT_TYPES.map(x => (
|
||||||
<LauncherIcon className="h-16 max-w-22 min-w-16" launcher={x} />
|
<button
|
||||||
<span className="text-lg font-medium">{x}</span>
|
className="flex flex-col items-center justify-center gap-y-4 rounded-md p-4 active:bg-border/10 hover:bg-border/05"
|
||||||
</button>
|
key={x}
|
||||||
))}
|
type="button"
|
||||||
</div>
|
>
|
||||||
<small className="pt-2 text-fg-secondary">
|
<LauncherIcon className="h-16 max-w-22 min-w-16" launcher={x} />
|
||||||
Want to contribute a launcher import? Click
|
<span className="text-lg font-medium">{x}</span>
|
||||||
{' '}
|
</button>
|
||||||
<a className='text-brand-pressed' href="https://github.com/Polyfrost/OneLauncher">here</a>
|
))}
|
||||||
.
|
</div>
|
||||||
</small>
|
<small className="pt-2 text-fg-secondary">
|
||||||
</div>
|
Want to contribute a launcher import? Click
|
||||||
</OnboardingStep>
|
{' '}
|
||||||
</>
|
<a className="text-brand-pressed" href="https://github.com/Polyfrost/OneLauncher">here</a>
|
||||||
)
|
.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</OnboardingStep>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,54 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import Illustration from '@/assets/illustrations/onboarding/language.svg';
|
||||||
import { OnboardingStep } from './route'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import Illustration from '../../assets/illustrations/onboarding/language.svg';
|
import { OnboardingStep } from './route';
|
||||||
|
|
||||||
export const Route = createFileRoute('/onboarding/language')({
|
export const Route = createFileRoute('/onboarding/language')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
{
|
{
|
||||||
lang: "English",
|
lang: 'English',
|
||||||
percentage: 100
|
percentage: 100,
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<OnboardingStep
|
<OnboardingStep
|
||||||
// importing and using it as a react component was not working
|
// importing and using it as a react component was not working
|
||||||
illustration={<img src={Illustration} />}
|
illustration={<img src={Illustration} />}
|
||||||
paragraph="Choose your preferred language."
|
paragraph="Choose your preferred language."
|
||||||
title="Language">
|
title="Language"
|
||||||
<div className="h-full w-full flex flex-col gap-y-3">
|
>
|
||||||
<div className="flex flex-col gap-y-2 rounded-lg bg-page-elevated">
|
<div className="h-full w-full flex flex-col gap-y-3">
|
||||||
<div className="max-h-84 overflow-hidden">
|
<div className="flex flex-col gap-y-2 rounded-lg bg-page-elevated">
|
||||||
<div className="flex flex-col gap-y-1 p-2">
|
<div className="max-h-84 overflow-hidden">
|
||||||
<aside>
|
<div className="flex flex-col gap-y-1 p-2">
|
||||||
{languages.map(x => (
|
<aside>
|
||||||
<div
|
{languages.map(x => (
|
||||||
className="flex flex-row items-center rounded-lg px-6 py-5"
|
<div
|
||||||
>
|
className="flex flex-row items-center rounded-lg px-6 py-5"
|
||||||
<div className="flex-1 text-lg font-medium">{x.lang}</div>
|
key={x.lang}
|
||||||
<div className="flex-1 text-right text-xs">
|
>
|
||||||
{x.percentage}
|
<div className="flex-1 text-lg font-medium">{x.lang}</div>
|
||||||
%
|
<div className="flex-1 text-right text-xs">
|
||||||
</div>
|
{x.percentage}
|
||||||
</div>
|
%
|
||||||
))}
|
</div>
|
||||||
</aside>
|
</div>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex flex-row justify-end">
|
<div className="w-full flex flex-row justify-end">
|
||||||
<p className="text-xs">Help translate OneLauncher</p>
|
<p className="text-xs">Help translate OneLauncher</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</OnboardingStep>
|
</OnboardingStep>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,65 @@
|
||||||
import { type MinecraftCredentials } from '@/bindings.gen'
|
import type { MinecraftCredentials } from '@/bindings.gen';
|
||||||
import Button from '@/components/base/Button'
|
import PlayerHead from '@/components/content/PlayerHead';
|
||||||
import { Show } from '@/components/base/Show'
|
import { bindings } from '@/main';
|
||||||
import PlayerHead from '@/components/content/PlayerHead'
|
import { useCommand } from '@onelauncher/common';
|
||||||
import useCommand from '@/hooks/useCommand'
|
import { Button, Show } from '@onelauncher/common/components';
|
||||||
import { bindings } from '@/main'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { useState } from 'react';
|
||||||
import { useState } from 'react'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/onboarding/login')({
|
export const Route = createFileRoute('/onboarding/login')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const [profile, setProfile] = useState<MinecraftCredentials>()
|
const [profile, setProfile] = useState<MinecraftCredentials>();
|
||||||
const [errorMessage, setErrorMessage] = useState<string>('')
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
const result = useCommand("beginMsFlow", bindings.commands.beginMsFlow, {
|
const result = useCommand('beginMsFlow', bindings.commands.beginMsFlow, {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
subscribed: false
|
subscribed: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
function beginMsAuthFlow() {
|
function beginMsAuthFlow() {
|
||||||
result.refetch()
|
result.refetch();
|
||||||
|
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
setErrorMessage(result.error.message)
|
setErrorMessage(result.error.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.data) {
|
if (!result.data) {
|
||||||
setErrorMessage('No account was found. Please try again.');
|
setErrorMessage('No account was found. Please try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProfile(result.data);
|
setProfile(result.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* trust me i'll fix ui issues later */}
|
{/* trust me i'll fix ui issues later */}
|
||||||
<div className="grid grid-cols-2 h-full w-full flex-col items-start justify-center gap-x-16 gap-y-4">
|
<div className="grid grid-cols-2 h-full w-full flex-col items-start justify-center gap-x-16 gap-y-4">
|
||||||
<h1 className="text-6xl -mb-2">Login</h1>
|
<h1 className="text-6xl -mb-2">Login</h1>
|
||||||
|
|
||||||
<h3>Before you continue, we require you to own a copy of Minecraft: Java Edition.</h3>
|
<h3>Before you continue, we require you to own a copy of Minecraft: Java Edition.</h3>
|
||||||
|
|
||||||
<Show when={profile}>
|
<Show when={profile}>
|
||||||
<div className="w-full flex flex-row items-center justify-start gap-x-3 border border-border/05 rounded-lg bg-component-bg p-3">
|
<div className="w-full flex flex-row items-center justify-start gap-x-3 border border-border/05 rounded-lg bg-component-bg p-3">
|
||||||
<PlayerHead className="rounded-md" uuid={profile?.id} />
|
<PlayerHead className="rounded-md" uuid={profile?.id} />
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-2">
|
||||||
<p className="text-lg text-fg-primary">{profile?.username}</p>
|
<p className="text-lg text-fg-primary">{profile?.username}</p>
|
||||||
<p className="text-fg-secondary">{profile?.id}</p>
|
<p className="text-fg-secondary">{profile?.id}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
children="Login with Microsoft"
|
children="Login with Microsoft"
|
||||||
onPress={beginMsAuthFlow}
|
onPress={beginMsAuthFlow}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="text-danger">{errorMessage}</p>
|
<p className="text-danger">{errorMessage}</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,140 +1,142 @@
|
||||||
import Button from '@/components/base/Button'
|
import type { JSX } from 'react';
|
||||||
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
|
import { Button } from '@onelauncher/common/components';
|
||||||
import { createContext, useState, type JSX } from 'react';
|
import { createFileRoute, Outlet, useNavigate, useRouterState } from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/onboarding')({
|
export const Route = createFileRoute('/onboarding')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
'/onboarding', // done
|
'/onboarding', // done
|
||||||
'/onboarding/language', // done (only ui)
|
'/onboarding/language', // done (only ui)
|
||||||
'/onboarding/login', // wip
|
'/onboarding/login', // wip
|
||||||
'/onboarding/import', // wip
|
'/onboarding/import', // wip
|
||||||
|
|
||||||
'/onboarding/summary', // wip
|
'/onboarding/summary', // wip
|
||||||
'/onboarding/complete', // wip
|
'/onboarding/complete', // wip
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type Language = {
|
interface Language {
|
||||||
lang: string,
|
lang: string;
|
||||||
percentage: number
|
percentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportInstancesType {
|
interface ImportInstancesType {
|
||||||
basePath: string;
|
basePath: string;
|
||||||
instances: string[];
|
instances: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnboardingContextType {
|
interface OnboardingContextType {
|
||||||
setLanguage: (language: Language) => void;
|
setLanguage: (language: Language) => void;
|
||||||
language: () => Language;
|
language: () => Language;
|
||||||
|
|
||||||
setImportInstances: (type: string, basePath: string, instances: string[]) => void;
|
setImportInstances: (type: string, basePath: string, instances: Array<string>) => void;
|
||||||
importInstances: (type: string) => ImportInstancesType | undefined;
|
importInstances: (type: string) => ImportInstancesType | undefined;
|
||||||
|
|
||||||
setForwardButtonEnabled: (enabled: boolean) => void;
|
setForwardButtonEnabled: (enabled: boolean) => void;
|
||||||
|
|
||||||
getTasks: () => string[];
|
getTasks: () => Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate();
|
||||||
const routerState = useRouterState()
|
const routerState = useRouterState();
|
||||||
|
|
||||||
const currentPath = routerState.location.pathname
|
const currentPath = routerState.location.pathname;
|
||||||
const currentStepIndex = steps.findIndex(path => currentPath === path || currentPath.startsWith(path))
|
const currentStepIndex = steps.findIndex(path => currentPath === path || currentPath.startsWith(path));
|
||||||
|
|
||||||
const progressPercentage =
|
const progressPercentage = currentStepIndex >= 0 && steps.length > 1
|
||||||
currentStepIndex >= 0 && steps.length > 1
|
? (currentStepIndex / (steps.length - 1)) * 100
|
||||||
? (currentStepIndex / (steps.length - 1)) * 100
|
: 0;
|
||||||
: 0;
|
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
console.log("Geri için mevcut yol:", currentPath);
|
console.log('Geri için mevcut yol:', currentPath);
|
||||||
console.log("Geri için mevcut adım indeksi:", currentStepIndex);
|
console.log('Geri için mevcut adım indeksi:', currentStepIndex);
|
||||||
|
|
||||||
if (currentStepIndex > 0) {
|
if (currentStepIndex > 0) {
|
||||||
const previousStep = steps[currentStepIndex - 1];
|
const previousStep = steps[currentStepIndex - 1];
|
||||||
console.log("Geri gidiliyor:", previousStep);
|
console.log('Geri gidiliyor:', previousStep);
|
||||||
navigate({ to: previousStep as any });
|
navigate({ to: previousStep as any });
|
||||||
} else {
|
}
|
||||||
console.log("Zaten ilk adımda veya adım bulunamadı, geri gidilemez.");
|
else {
|
||||||
}
|
console.log('Zaten ilk adımda veya adım bulunamadı, geri gidilemez.');
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
console.log("İleri için mevcut yol:", currentPath);
|
console.log('İleri için mevcut yol:', currentPath);
|
||||||
console.log("İleri için mevcut adım indeksi:", currentStepIndex);
|
console.log('İleri için mevcut adım indeksi:', currentStepIndex);
|
||||||
|
|
||||||
if (currentStepIndex === -1) {
|
if (currentStepIndex === -1) {
|
||||||
navigate({ to: steps[0] as any });
|
navigate({ to: steps[0] as any });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStepIndex < steps.length - 1) {
|
if (currentStepIndex < steps.length - 1) {
|
||||||
const nextStep = steps[currentStepIndex + 1];
|
const nextStep = steps[currentStepIndex + 1];
|
||||||
console.log("İleri gidiliyor:", nextStep);
|
console.log('İleri gidiliyor:', nextStep);
|
||||||
navigate({ to: nextStep as any });
|
navigate({ to: nextStep as any });
|
||||||
} else if (currentStepIndex === steps.length - 1) {
|
}
|
||||||
console.log("Onboarding tamamlandı, /app adresine gidiliyor");
|
else if (currentStepIndex === steps.length - 1) {
|
||||||
navigate({ to: '/app' as any });
|
console.log('Onboarding tamamlandı, /app adresine gidiliyor');
|
||||||
} else {
|
navigate({ to: '/app' as any });
|
||||||
console.log("Bir sonraki adım belirlenemedi.");
|
}
|
||||||
}
|
else {
|
||||||
};
|
console.log('Bir sonraki adım belirlenemedi.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// remind me 2 hours! i'll fix this
|
// remind me 2 hours! i'll fix this
|
||||||
// update: it's fixed!
|
// update: it's fixed!
|
||||||
<div className="w-full flex flex-col items-center h-screen">
|
<div className="w-full flex flex-col items-center h-screen">
|
||||||
<div className="h-0.5 w-full">
|
<div className="h-0.5 w-full">
|
||||||
<div
|
<div
|
||||||
className="h-full rounded-lg bg-brand transition-all"
|
className="h-full rounded-lg bg-brand transition-all"
|
||||||
style={{
|
style={{
|
||||||
width: `${progressPercentage}%`,
|
width: `${progressPercentage}%`,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 max-w-280 w-full flex flex-col gap-y-4 p-8">
|
<div className="flex-1 max-w-280 w-full flex flex-col gap-y-4 p-8">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full max-w-280 p-8">
|
<div className="w-full max-w-280 p-8">
|
||||||
<div className="w-1/3 flex flex-row items-stretch gap-x-8 [&>*]:w-full ml-auto">
|
<div className="w-1/3 flex flex-row items-stretch gap-x-8 [&>*]:w-full ml-auto">
|
||||||
<Button onClick={handleBack}>Back</Button>
|
<Button onClick={handleBack}>Back</Button>
|
||||||
<Button onClick={handleNext}>{currentStepIndex === steps.length - 1 ? 'Finish' : 'Next'}</Button>
|
<Button onClick={handleNext}>{currentStepIndex === steps.length - 1 ? 'Finish' : 'Next'}</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnboardingStepProps {
|
export interface OnboardingStepProps {
|
||||||
title: string;
|
title: string;
|
||||||
paragraph: string;
|
paragraph: string;
|
||||||
illustration: JSX.Element;
|
illustration: JSX.Element;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingStep(props: OnboardingStepProps) {
|
export function OnboardingStep(props: OnboardingStepProps) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 h-full w-full gap-x-16">
|
<div className="grid grid-cols-2 h-full w-full gap-x-16">
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
{props.illustration}
|
{props.illustration}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col justify-center gap-y-4">
|
<div className="flex flex-col justify-center gap-y-4">
|
||||||
<div className="w-full flex flex-col gap-y-2">
|
<div className="w-full flex flex-col gap-y-2">
|
||||||
<h1 className="text-2xl">{props.title}</h1>
|
<h1 className="text-2xl">{props.title}</h1>
|
||||||
<p className="text-lg text-fg-secondary line-height-normal">{props.paragraph}</p>
|
<p className="text-lg text-fg-secondary line-height-normal">{props.paragraph}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-h-96 w-full flex flex-1 flex-col gap-y-2">
|
<div className="max-h-96 w-full flex flex-1 flex-col gap-y-2">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
|
||||||
export const Route = createFileRoute('/onboarding/summary')({
|
export const Route = createFileRoute('/onboarding/summary')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/onboarding/summary"!</div>
|
return <div>Hello "/onboarding/summary"!</div>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "tailwindcss-react-aria-components";
|
@plugin "tailwindcss-react-aria-components";
|
||||||
@plugin "tailwindcss-animated";
|
@plugin "tailwindcss-animated";
|
||||||
|
@source "../../node_modules/@onelauncher/common";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
/* foreground colors */
|
/* foreground colors */
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
(() => {
|
|
||||||
if (location.hostname !== 'localhost')
|
|
||||||
return;
|
|
||||||
|
|
||||||
window.addEventListener('keypress', (e) => {
|
|
||||||
if (e.getModifierState('Control') && e.key === 'r') {
|
|
||||||
e.preventDefault();
|
|
||||||
window.location.reload(); // tauri disables this i think
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export const LAUNCHER_IMPORT_TYPES: string[] = [
|
export const LAUNCHER_IMPORT_TYPES: Array<string> = [
|
||||||
'PrismLauncher',
|
'PrismLauncher',
|
||||||
'Curseforge',
|
'Curseforge',
|
||||||
// 'Modrinth',
|
// 'Modrinth',
|
||||||
|
|
@ -8,4 +8,4 @@ export const LAUNCHER_IMPORT_TYPES: string[] = [
|
||||||
'MultiMC',
|
'MultiMC',
|
||||||
// 'TLauncher',
|
// 'TLauncher',
|
||||||
// 'Technic'
|
// 'Technic'
|
||||||
] as const;
|
] as const;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
document.addEventListener('contextmenu', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
@ -15,7 +15,7 @@ export const THEMES = {
|
||||||
} as const satisfies Record<string, Theme>;
|
} as const satisfies Record<string, Theme>;
|
||||||
|
|
||||||
export interface Theme {
|
export interface Theme {
|
||||||
variants: ThemeVariant[];
|
variants: Array<ThemeVariant>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ThemeTypes = 'dark' | 'light';
|
export type ThemeTypes = 'dark' | 'light';
|
||||||
|
|
@ -23,4 +23,4 @@ export type ThemeTypes = 'dark' | 'light';
|
||||||
export interface ThemeVariant {
|
export interface ThemeVariant {
|
||||||
type: ThemeTypes;
|
type: ThemeTypes;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -24,15 +24,17 @@
|
||||||
"prep": "pnpm scripts prep",
|
"prep": "pnpm scripts prep",
|
||||||
"typecheck": "pnpm -r typecheck",
|
"typecheck": "pnpm -r typecheck",
|
||||||
"bootstrap": "cargo clean && ./packages/scripts/setup.sh && pnpm install && pnpm prep && pnpm codegen",
|
"bootstrap": "cargo clean && ./packages/scripts/setup.sh && pnpm install && pnpm prep && pnpm codegen",
|
||||||
|
|
||||||
|
"core": "pnpm --filter @onelauncher/core -- ",
|
||||||
"migrate": "sea-orm-cli migrate -d ./packages/migration",
|
"migrate": "sea-orm-cli migrate -d ./packages/migration",
|
||||||
"entity:gen": "sea-orm-cli generate entity -o packages/entity/src/entities --with-serde both --model-extra-attributes 'onelauncher_macro::specta' -t",
|
"entity:gen": "sea-orm-cli generate entity -o packages/entity/src/entities --with-serde both --model-extra-attributes 'onelauncher_macro::specta' -t",
|
||||||
"onelauncher:frontend": "pnpm --filter @onelauncher/frontend -- ",
|
"onelauncher:frontend": "pnpm --filter @onelauncher/frontend -- ",
|
||||||
"onelauncher:desktop": "pnpm --filter @onelauncher/desktop -- ",
|
"onelauncher:desktop": "pnpm --filter @onelauncher/desktop -- ",
|
||||||
"oneclient:frontend": "pnpm --filter @oneclient/frontend -- ",
|
"oneclient:frontend": "pnpm --filter @oneclient/frontend -- ",
|
||||||
"oneclient:desktop": "pnpm --filter @oneclient/desktop -- ",
|
"oneclient:desktop": "pnpm --filter @oneclient/desktop -- ",
|
||||||
"client": "pnpm --filter @onelauncher/client -- ",
|
"web_common": "pnpm --filter @onelauncher/common -- ",
|
||||||
"core": "pnpm --filter @onelauncher/core -- ",
|
|
||||||
"scripts": "pnpm --filter @onelauncher/scripts -- ",
|
"scripts": "pnpm --filter @onelauncher/scripts -- ",
|
||||||
|
|
||||||
"format": "pnpm format:es && pnpm format:rs",
|
"format": "pnpm format:es && pnpm format:rs",
|
||||||
"format:es": "pnpm lint:es --fix",
|
"format:es": "pnpm lint:es --fix",
|
||||||
"format:rs": "cargo +nightly fmt && cargo clippy --fix",
|
"format:rs": "cargo +nightly fmt && cargo clippy --fix",
|
||||||
|
|
|
||||||
3
packages/web_common/.gitignore
vendored
Normal file
3
packages/web_common/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
lib/
|
||||||
|
dist/
|
||||||
|
out/
|
||||||
6
packages/web_common/README.md
Normal file
6
packages/web_common/README.md
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
### Registering TailwindCSS
|
||||||
|
In your main tailwind css config file, use the following to register the components as a source:
|
||||||
|
(relative to the config file)
|
||||||
|
```css
|
||||||
|
@source "../../node_modules/@onelauncher/common";
|
||||||
|
```
|
||||||
42
packages/web_common/package.json
Normal file
42
packages/web_common/package.json
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "@onelauncher/common",
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Common web utilities for this monorepo",
|
||||||
|
"authors": "Polyfrost",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./lib/index.d.ts",
|
||||||
|
"import": "./lib/index.js"
|
||||||
|
},
|
||||||
|
"./components": {
|
||||||
|
"types": "./lib/components.d.ts",
|
||||||
|
"import": "./lib/components.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./lib/index.js",
|
||||||
|
"typings": "./lib/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@tanstack/react-query": "^5.66.5",
|
||||||
|
"@tanstack/react-router": "^1.114.3",
|
||||||
|
"motion": "^12.10.5",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-aria-components": "^1.8.0",
|
||||||
|
"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": {
|
||||||
|
"@types/react": "^19.0.8",
|
||||||
|
"esbuild": "^0.25.5",
|
||||||
|
"rollup": "^4.41.1",
|
||||||
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
|
"typescript": "^5.8.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
82
packages/web_common/rollup.config.mjs
Normal file
82
packages/web_common/rollup.config.mjs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import process from 'node:process';
|
||||||
|
import dts from 'rollup-plugin-dts';
|
||||||
|
import esbuild from 'rollup-plugin-esbuild';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {boolean} true if the id is an external module
|
||||||
|
*/
|
||||||
|
const isExternal
|
||||||
|
= process.platform === 'win32'
|
||||||
|
? (/** @type {string} */ id) => !/^(?:[a-z]:\\|[.\\])/i.test(id)
|
||||||
|
: (/** @type {string} */ id) => !/^[./]/.test(id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @param {import('rollup').RollupOptions} config
|
||||||
|
* @returns {import('rollup').RollupOptions} bundle options
|
||||||
|
*/
|
||||||
|
function bundle(input, config) {
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
input,
|
||||||
|
external: isExternal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default [
|
||||||
|
// Output for NodeJS
|
||||||
|
bundle('./src/index.ts', {
|
||||||
|
plugins: [esbuild({ target: 'es2020' })],
|
||||||
|
output: {
|
||||||
|
file: `./lib/index.js`,
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: false,
|
||||||
|
compact: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Output for Typescript's .d.ts
|
||||||
|
bundle('./src/index.ts', {
|
||||||
|
plugins: [dts()],
|
||||||
|
output: {
|
||||||
|
file: `./lib/index.d.ts`,
|
||||||
|
format: 'esm',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Output for React components
|
||||||
|
bundle('./src/components/index.ts', {
|
||||||
|
plugins: [esbuild({ target: 'es2020' })],
|
||||||
|
output: {
|
||||||
|
file: `./lib/components.js`,
|
||||||
|
format: 'esm',
|
||||||
|
sourcemap: false,
|
||||||
|
compact: false,
|
||||||
|
},
|
||||||
|
jsx: 'react-jsx',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Output for React components .d.ts
|
||||||
|
bundle('./src/components/index.ts', {
|
||||||
|
plugins: [dts()],
|
||||||
|
output: {
|
||||||
|
file: `./lib/components.d.ts`,
|
||||||
|
format: 'esm',
|
||||||
|
},
|
||||||
|
jsx: 'react-jsx',
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Output for browser
|
||||||
|
// bundle({
|
||||||
|
// plugins: [esbuild({ target: 'es2020', minify: true })],
|
||||||
|
// output: {
|
||||||
|
// file: `./dist/${name}-v${version}.js`,
|
||||||
|
// format: 'esm',
|
||||||
|
// name: camelCaseName,
|
||||||
|
// sourcemap: true,
|
||||||
|
// compact: true,
|
||||||
|
// },
|
||||||
|
// jsx: 'react-jsx',
|
||||||
|
// }),
|
||||||
|
];
|
||||||
|
|
@ -35,16 +35,16 @@ export const buttonVariants = tv({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
export type ButtonVariantsProps = VariantProps<typeof buttonVariants>;
|
||||||
|
|
||||||
export type ButtonProps = AriaButtonProps & RefAttributes<HTMLButtonElement> & {
|
export type ButtonProps = AriaButtonProps & RefAttributes<HTMLButtonElement> & {
|
||||||
color?: ButtonVariants['color'];
|
color?: ButtonVariantsProps['color'];
|
||||||
size?: ButtonVariants['size'];
|
size?: ButtonVariantsProps['size'];
|
||||||
className?: string;
|
className?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function Button({
|
export function Button({
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
size = 'normal',
|
size = 'normal',
|
||||||
className,
|
className,
|
||||||
|
|
@ -58,5 +58,3 @@ function Button({
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Button;
|
|
||||||
|
|
@ -4,14 +4,14 @@ import { useEffect, useRef, useState } from 'react';
|
||||||
import { Menu, MenuItem, Popover } from 'react-aria-components';
|
import { Menu, MenuItem, Popover } from 'react-aria-components';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
interface ContextMenuProps<T extends object> extends MenuProps<T> {
|
export interface ContextMenuProps<T extends object> extends MenuProps<T> {
|
||||||
className?: string;
|
className?: string;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
triggerRef: RefObject<HTMLElement | null>;
|
triggerRef: RefObject<HTMLElement | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextMenu<T extends object>({
|
export function ContextMenu<T extends object>({
|
||||||
className,
|
className,
|
||||||
isOpen,
|
isOpen,
|
||||||
setOpen,
|
setOpen,
|
||||||
|
|
@ -87,6 +87,4 @@ function ContextMenu<T extends object>({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ContextMenu;
|
|
||||||
|
|
||||||
ContextMenu.Item = <T extends object>({ className, ...rest }: MenuItemProps<T>) => <MenuItem {...rest} />;
|
ContextMenu.Item = <T extends object>({ className, ...rest }: MenuItemProps<T>) => <MenuItem {...rest} />;
|
||||||
65
packages/web_common/src/components/Dropdown.tsx
Normal file
65
packages/web_common/src/components/Dropdown.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type {
|
||||||
|
SelectProps as AriaComboBoxProps,
|
||||||
|
ListBoxItemProps as AriaListBoxItemProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import type { VariantProps } from 'tailwind-variants';
|
||||||
|
import type { ClassNameString } from '../types/global';
|
||||||
|
import {
|
||||||
|
Select as AriaComboBox,
|
||||||
|
Label as AriaLabel,
|
||||||
|
ListBox as AriaListBox,
|
||||||
|
ListBoxItem as AriaListBoxItem,
|
||||||
|
Popover as AriaPopover,
|
||||||
|
SelectValue,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { tv } from 'tailwind-variants';
|
||||||
|
import { Button } from '.';
|
||||||
|
|
||||||
|
export interface ComboBoxWrapperProps<T extends object> extends Omit<AriaComboBoxProps<T>, 'className'>, ClassNameString {
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdownVariants = tv({
|
||||||
|
base: [
|
||||||
|
'flex flex-col border border-component-bg-hover w-fit rounded-lg focus:outline-none',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DropdownVariantsProps = VariantProps<typeof dropdownVariants>;
|
||||||
|
|
||||||
|
export function Dropdown<T extends object>({
|
||||||
|
className,
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
...rest
|
||||||
|
}: ComboBoxWrapperProps<T>) {
|
||||||
|
return (
|
||||||
|
<AriaComboBox
|
||||||
|
className={dropdownVariants({ className })}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{label && <AriaLabel>{label}</AriaLabel>}
|
||||||
|
<div className="flex">
|
||||||
|
<SelectValue />
|
||||||
|
<Button className="size-6">▼</Button>
|
||||||
|
</div>
|
||||||
|
<AriaPopover className="border border-orange-400">
|
||||||
|
<AriaListBox className="bg-blue-500 p-2 w-38">
|
||||||
|
{children}
|
||||||
|
</AriaListBox>
|
||||||
|
</AriaPopover>
|
||||||
|
</AriaComboBox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dropdown;
|
||||||
|
|
||||||
|
Dropdown.Item = ({
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}: AriaListBoxItemProps & ClassNameString) => (
|
||||||
|
<AriaListBoxItem
|
||||||
|
className={className}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import type { ClassNameString } from '@/types/global';
|
|
||||||
import type { MenuItemProps as AriaMenuItemProps, MenuProps as AriaMenuProps, PopoverProps as AriaPopoverProps } from 'react-aria-components';
|
import type { MenuItemProps as AriaMenuItemProps, MenuProps as AriaMenuProps, PopoverProps as AriaPopoverProps } from 'react-aria-components';
|
||||||
|
import type { ClassNameString } from '../types/global';
|
||||||
import { Menu as AriaMenu, MenuItem as AriaMenuItem, Popover as AriaPopover } from 'react-aria-components';
|
import { Menu as AriaMenu, MenuItem as AriaMenuItem, Popover as AriaPopover } from 'react-aria-components';
|
||||||
import { twMerge } from 'tailwind-merge';
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
function Menu<T extends object>({
|
export function Menu<T extends object>({
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
}: AriaMenuProps<T> & ClassNameString) {
|
}: AriaMenuProps<T> & ClassNameString) {
|
||||||
|
|
@ -18,8 +18,6 @@ function Menu<T extends object>({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Menu;
|
|
||||||
|
|
||||||
Menu.Popover = ({
|
Menu.Popover = ({
|
||||||
className,
|
className,
|
||||||
...rest
|
...rest
|
||||||
34
packages/web_common/src/components/Show.tsx
Normal file
34
packages/web_common/src/components/Show.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface ShowProps<T = any> {
|
||||||
|
/**
|
||||||
|
* Condition to evaluate.
|
||||||
|
* If truthy, `children` are rendered.
|
||||||
|
* If T is a value (not just boolean), you might want to use it in children,
|
||||||
|
* but this basic version doesn't automatically pass it like SolidJS does for type narrowing.
|
||||||
|
*/
|
||||||
|
when: T | boolean | undefined | null;
|
||||||
|
/**
|
||||||
|
* Content to render if `when` is falsy.
|
||||||
|
*/
|
||||||
|
fallback?: React.ReactNode;
|
||||||
|
/**
|
||||||
|
* Content to render if `when` is truthy.
|
||||||
|
*/
|
||||||
|
children: React.ReactNode | ((item: NonNullable<T>) => React.ReactNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShowComponent<T,>({ when, fallback = null, children }: ShowProps<T>): JSX.Element | null {
|
||||||
|
if (when) {
|
||||||
|
if (typeof children === 'function')
|
||||||
|
// Ensure 'when' is not null or undefined before calling the function.
|
||||||
|
// This mimics Solid's behavior where children can be a function that receives the truthy 'when' value.
|
||||||
|
return <>{children(when as NonNullable<T>)}</>;
|
||||||
|
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
return <>{fallback}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Show = React.memo(ShowComponent) as <T>(props: ShowProps<T>) => JSX.Element | null;
|
||||||
43
packages/web_common/src/components/Switch.tsx
Normal file
43
packages/web_common/src/components/Switch.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type React from 'react';
|
||||||
|
import type { RefAttributes } from 'react';
|
||||||
|
import type { SwitchProps as AriaSwitchProps } from 'react-aria-components';
|
||||||
|
import type { VariantProps } from 'tailwind-variants';
|
||||||
|
import { Switch as AriaSwitch } from 'react-aria-components';
|
||||||
|
import { tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const switchVariants = tv({
|
||||||
|
base: [
|
||||||
|
'group inline-flex h-6 w-11 items-center rounded-full transition-colors',
|
||||||
|
'bg-component-bg-pressed data-[selected]:bg-blue-600',
|
||||||
|
'focus:outline-none',
|
||||||
|
'disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const switchThumbVariants = tv({
|
||||||
|
base: [
|
||||||
|
'pointer-events-none inline-block h-4 w-4 rounded-full bg-white shadow ring-0 transition-transform',
|
||||||
|
'translate-x-1 group-data-[selected]:translate-x-6',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SwitchVariantsProps = VariantProps<typeof switchVariants>;
|
||||||
|
export type SwitchThumbVariantsProps = VariantProps<typeof switchThumbVariants>;
|
||||||
|
|
||||||
|
export type SwitchProps = AriaSwitchProps & RefAttributes<HTMLLabelElement> & {
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Switch({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: SwitchProps) {
|
||||||
|
return (
|
||||||
|
<AriaSwitch className={switchVariants({ className })} {...props}>
|
||||||
|
<span className={switchThumbVariants()} />
|
||||||
|
{children && <span className="ml-3 text-sm font-medium text-gray-900">{children}</span>}
|
||||||
|
</AriaSwitch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Input } from 'react-aria-components';
|
|
||||||
import type { RefAttributes } from 'react';
|
import type { RefAttributes } from 'react';
|
||||||
import type { InputProps as AriaInputProps } from 'react-aria-components';
|
import type { InputProps as AriaInputProps } from 'react-aria-components';
|
||||||
import type { VariantProps } from 'tailwind-variants';
|
import type { VariantProps } from 'tailwind-variants';
|
||||||
|
import { Input } from 'react-aria-components';
|
||||||
import { tv } from 'tailwind-variants';
|
import { tv } from 'tailwind-variants';
|
||||||
|
|
||||||
export const textFieldVariants = tv({
|
export const textFieldVariants = tv({
|
||||||
|
|
@ -16,18 +16,18 @@ export const textFieldVariants = tv({
|
||||||
'text-sm text-fg-primary font-medium',
|
'text-sm text-fg-primary font-medium',
|
||||||
'bg-transparent outline-none appearance-none p-0 m-0 box-border w-full font-medium',
|
'bg-transparent outline-none appearance-none p-0 m-0 box-border w-full font-medium',
|
||||||
'[&::selection]:bg-brand/30',
|
'[&::selection]:bg-brand/30',
|
||||||
'placeholder:text-fg-secondary/40 placeholder:font-normal'
|
'placeholder:text-fg-secondary/40 placeholder:font-normal',
|
||||||
],
|
],
|
||||||
variants: {
|
variants: {
|
||||||
invalid: {
|
invalid: {
|
||||||
true: {
|
true: {
|
||||||
base: 'border-danger'
|
base: 'border-danger',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export type TextFieldVariants = VariantProps<typeof textFieldVariants>;
|
export type TextFieldVariantsProps = VariantProps<typeof textFieldVariants>;
|
||||||
|
|
||||||
export type TextFieldProps = AriaInputProps & RefAttributes<HTMLInputElement> & {
|
export type TextFieldProps = AriaInputProps & RefAttributes<HTMLInputElement> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
@ -41,5 +41,5 @@ export function TextField({
|
||||||
<>
|
<>
|
||||||
<Input className={textFieldVariants({ className })} {...props} />
|
<Input className={textFieldVariants({ className })} {...props} />
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
import type { RefAttributes } from 'react';
|
import type { RefAttributes } from 'react';
|
||||||
import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components';
|
import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components';
|
||||||
import type { ButtonVariants } from './Button';
|
import type { ButtonVariantsProps } from './Button';
|
||||||
import { ToggleButton as AriaToggleButton } from 'react-aria-components';
|
import { ToggleButton as AriaToggleButton } from 'react-aria-components';
|
||||||
import { buttonVariants } from './Button';
|
import { buttonVariants } from './Button';
|
||||||
|
|
||||||
export type ToggleButtonProps = AriaToggleButtonProps & RefAttributes<HTMLButtonElement> & {
|
export type ToggleButtonProps = AriaToggleButtonProps & RefAttributes<HTMLButtonElement> & {
|
||||||
color?: ButtonVariants['color'];
|
color?: ButtonVariantsProps['color'];
|
||||||
size?: ButtonVariants['size'];
|
size?: ButtonVariantsProps['size'];
|
||||||
className?: string;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function ToggleButton({
|
export function ToggleButton({
|
||||||
color = 'ghost',
|
color = 'ghost',
|
||||||
size = 'normal',
|
size = 'normal',
|
||||||
className,
|
className,
|
||||||
|
|
@ -20,5 +20,3 @@ function ToggleButton({
|
||||||
<AriaToggleButton className={buttonVariants({ color, size, className })} {...props} />
|
<AriaToggleButton className={buttonVariants({ color, size, className })} {...props} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ToggleButton;
|
|
||||||
66
packages/web_common/src/components/Tooltip.tsx
Normal file
66
packages/web_common/src/components/Tooltip.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import type React from 'react';
|
||||||
|
import type { RefAttributes } from 'react';
|
||||||
|
import type { TooltipTriggerComponentProps as AriaTooltipTriggerProps } from 'react-aria-components';
|
||||||
|
import type { VariantProps } from 'tailwind-variants';
|
||||||
|
import {
|
||||||
|
Tooltip as AriaTooltip,
|
||||||
|
TooltipTrigger as AriaTooltipTrigger,
|
||||||
|
OverlayArrow,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { tv } from 'tailwind-variants';
|
||||||
|
|
||||||
|
export const tooltipVariants = tv({
|
||||||
|
base: [
|
||||||
|
'px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-lg',
|
||||||
|
'border border-gray-700 max-w-xs z-50',
|
||||||
|
'data-[entering]:animate-in data-[entering]:fade-in data-[entering]:zoom-in-95',
|
||||||
|
'data-[exiting]:animate-out data-[exiting]:fade-out data-[exiting]:zoom-out-95',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const tooltipArrowVariants = tv({
|
||||||
|
base: [
|
||||||
|
'fill-gray-900 stroke-gray-700 stroke-1',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TooltipVariantProps = VariantProps<typeof tooltipVariants>;
|
||||||
|
export type TooltipArrowVariantProps = VariantProps<typeof tooltipArrowVariants>;
|
||||||
|
|
||||||
|
export type TooltipProps = AriaTooltipTriggerProps & RefAttributes<HTMLElement> & {
|
||||||
|
text: string;
|
||||||
|
children: React.ReactElement;
|
||||||
|
placement?: 'top' | 'bottom' | 'left' | 'right';
|
||||||
|
className?: string;
|
||||||
|
delay?: number;
|
||||||
|
closeDelay?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Tooltip({
|
||||||
|
text,
|
||||||
|
children,
|
||||||
|
placement = 'top',
|
||||||
|
className,
|
||||||
|
delay = 700,
|
||||||
|
closeDelay = 0,
|
||||||
|
...props
|
||||||
|
}: TooltipProps) {
|
||||||
|
return (
|
||||||
|
<AriaTooltipTrigger closeDelay={closeDelay} delay={delay} {...props}>
|
||||||
|
{children}
|
||||||
|
<AriaTooltip className={tooltipVariants({ className })} placement={placement}>
|
||||||
|
<OverlayArrow>
|
||||||
|
<svg
|
||||||
|
className={tooltipArrowVariants()}
|
||||||
|
height={8}
|
||||||
|
viewBox="0 0 8 8"
|
||||||
|
width={8}
|
||||||
|
>
|
||||||
|
<path d="M0 0 L4 4 L8 0" />
|
||||||
|
</svg>
|
||||||
|
</OverlayArrow>
|
||||||
|
{text}
|
||||||
|
</AriaTooltip>
|
||||||
|
</AriaTooltipTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
10
packages/web_common/src/components/index.ts
Normal file
10
packages/web_common/src/components/index.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export * from './AnimatedOutlet';
|
||||||
|
export * from './Button';
|
||||||
|
export * from './ContextMenu';
|
||||||
|
export * from './Dropdown';
|
||||||
|
export * from './Menu';
|
||||||
|
export * from './Show';
|
||||||
|
export * from './Switch';
|
||||||
|
export * from './TextField';
|
||||||
|
export * from './ToggleButton';
|
||||||
|
export * from './Tooltip';
|
||||||
1
packages/web_common/src/hooks/index.ts
Normal file
1
packages/web_common/src/hooks/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './useCommand';
|
||||||
28
packages/web_common/src/hooks/useCommand.ts
Normal file
28
packages/web_common/src/hooks/useCommand.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import type { UndefinedInitialDataOptions, UseQueryResult } from '@tanstack/react-query';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
type CacheKey = string;
|
||||||
|
|
||||||
|
export function useCommand<T>(cacheKey: CacheKey | (string & {}), command: () => Promise<T>, options?: Omit<UndefinedInitialDataOptions<T>, 'queryKey' | 'queryFn'>): UseQueryResult<T, Error> {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [cacheKey],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const result = await command();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// TODO: (lynith) handle errors better (requires modifications to TauRPC)
|
||||||
|
catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e instanceof Error)
|
||||||
|
return Promise.reject(e);
|
||||||
|
else
|
||||||
|
return Promise.reject(new Error('Unknown error'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled: true,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// export useCommand;
|
||||||
2
packages/web_common/src/index.ts
Normal file
2
packages/web_common/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './hooks';
|
||||||
|
export * from './utils';
|
||||||
3
packages/web_common/src/types/global.d.ts
vendored
Normal file
3
packages/web_common/src/types/global.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export interface ClassNameString {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
17
packages/web_common/src/utils/dev.ts
Normal file
17
packages/web_common/src/utils/dev.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/* eslint-disable node/prefer-global/process -- we dont need imports this way */
|
||||||
|
const env = (import.meta as unknown as { env: { DEV: boolean; MODE: string } }).env;
|
||||||
|
|
||||||
|
export const isDev = env.DEV
|
||||||
|
|| env.MODE === 'development'
|
||||||
|
|| (typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'))
|
||||||
|
|| (typeof globalThis.process !== 'undefined' && globalThis.process.env.NODE_ENV === 'development');
|
||||||
|
|
||||||
|
export function registerDevExperience() {
|
||||||
|
// Enable CTRL+R to reload the page in dev mode
|
||||||
|
window.addEventListener('keypress', (e) => {
|
||||||
|
if (e.getModifierState('Control') && e.key === 'r') {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.reload(); // tauri disables this
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
2
packages/web_common/src/utils/index.ts
Normal file
2
packages/web_common/src/utils/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './dev';
|
||||||
|
export * from './nativeExperience';
|
||||||
6
packages/web_common/src/utils/nativeExperience.ts
Normal file
6
packages/web_common/src/utils/nativeExperience.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
export function registerNativeExperience() {
|
||||||
|
// disable browser right-click context menu
|
||||||
|
document.addEventListener('contextmenu', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
15
packages/web_common/tsconfig.json
Normal file
15
packages/web_common/tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*", "rollup.config.mjs"]
|
||||||
|
}
|
||||||
561
pnpm-lock.yaml
generated
561
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue