chore: merge onelauncher/main
This commit is contained in:
commit
d7ca81c2ed
78 changed files with 1915 additions and 763 deletions
|
|
@ -20,7 +20,7 @@ interface TargetConfig {
|
|||
}
|
||||
interface BuildTarget {
|
||||
updater: false | Updater;
|
||||
standalone: TargetConfig[];
|
||||
standalone: Array<TargetConfig>;
|
||||
}
|
||||
|
||||
const osTargets = {
|
||||
|
|
@ -67,7 +67,7 @@ const frontendBundle = 'apps/frontend/dist.tar.xz';
|
|||
const updaterName = `OneLauncher-Updater-${os}-${arch}`;
|
||||
const frontendName = `OneLauncher-Frontend-${os}-${arch}`;
|
||||
|
||||
const globFiles = async (pattern: string): Promise<string[]> => await (await glob.create(pattern)).glob();
|
||||
const globFiles = async (pattern: string): Promise<Array<string>> => await (await glob.create(pattern)).glob();
|
||||
|
||||
async function uploadFrontend() {
|
||||
if (!(await exists(frontendBundle))) {
|
||||
|
|
|
|||
6
.github/actions/setup-pnpm/action.yml
vendored
6
.github/actions/setup-pnpm/action.yml
vendored
|
|
@ -9,10 +9,12 @@ runs:
|
|||
using: composite
|
||||
steps:
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@ac5bf11548bf5e19b8aadb8182072616590fa4a6
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@26961cf329f22f6837d5f54c3efd76b480300ace
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
check-latest: true
|
||||
|
|
|
|||
20
.github/actions/setup-rust/action.yml
vendored
20
.github/actions/setup-rust/action.yml
vendored
|
|
@ -15,23 +15,23 @@ inputs:
|
|||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install Rust Stable
|
||||
uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ inputs.target }}
|
||||
components: clippy, rustfmt
|
||||
# - name: Install Rust Stable
|
||||
# uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
|
||||
# with:
|
||||
# toolchain: stable
|
||||
# targets: ${{ inputs.target }}
|
||||
# components: clippy, rustfmt
|
||||
|
||||
- name: Install Rust Nightly
|
||||
uses: dtolnay/rust-toolchain@7b1c307e0dcbda6122208f10795a713336a9b35a
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly
|
||||
toolchain: nightly-2025-07-23
|
||||
targets: ${{ inputs.target }}
|
||||
components: clippy, rustfmt
|
||||
components: cargo, clippy, rustc, rustfmt
|
||||
|
||||
- name: Cache Rust
|
||||
if: ${{ inputs.restore-cache == 'true' }}
|
||||
uses: Swatinem/rust-cache@9bdad043e88c75890e36ad3bbc8d27f0090dd609
|
||||
uses: Swatinem/rust-cache@master
|
||||
with:
|
||||
key: ${{ inputs.target }}
|
||||
save-if: ${{ inputs.save-cache }}
|
||||
|
|
|
|||
3
.github/workflows/oneclient_release.yml
vendored
3
.github/workflows/oneclient_release.yml
vendored
|
|
@ -43,5 +43,6 @@ jobs:
|
|||
releaseName: OneClient __VERSION__
|
||||
releaseDraft: true
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-alpha') || false }}
|
||||
tauriScript: pnpm oneclient:desktop
|
||||
tauriScript: pnpm -w oneclient:desktop
|
||||
args: ${{ matrix.args }}
|
||||
projectPath: ./apps/oneclient/desktop/
|
||||
|
|
|
|||
3
.github/workflows/onelauncher_release.yml
vendored
3
.github/workflows/onelauncher_release.yml
vendored
|
|
@ -43,5 +43,6 @@ jobs:
|
|||
releaseName: OneLauncher __VERSION__
|
||||
releaseDraft: true
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '-alpha') || false }}
|
||||
tauriScript: pnpm onelauncher:desktop
|
||||
tauriScript: pnpm -w onelauncher:desktop
|
||||
args: ${{ matrix.args }}
|
||||
projectPath: ./apps/onelauncher/desktop/
|
||||
|
|
|
|||
1
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
|
|
@ -0,0 +1 @@
|
|||
pnpm lint-staged
|
||||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
|||
20
|
||||
24
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ We welcome contributions! Please read our [contributing guidelines](CONTRIBUTING
|
|||
|
||||
If you encounter any issues, ensure that you are using the following versions (or later) of Rust, Node and Pnpm:
|
||||
|
||||
- `rustc` version: **1.89**
|
||||
- `rustc` version: **nightly-2025-07-23**
|
||||
- `node` version: **22.15**
|
||||
- `pnpm` version: **10.13**
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use onelauncher_core::{entity::clusters, error::LauncherResult};
|
||||
use onelauncher_core::entity::clusters;
|
||||
use onelauncher_core::error::LauncherResult;
|
||||
use tauri::Runtime;
|
||||
|
||||
#[taurpc::procedures(path = "oneclient", export_to = "../frontend/src/bindings.gen.ts")]
|
||||
|
|
@ -22,7 +23,9 @@ impl OneClientApi for OneClientApiImpl {
|
|||
webview_window.open_devtools();
|
||||
}
|
||||
|
||||
async fn get_clusters_grouped_by_major(self) -> LauncherResult<HashMap<u32, Vec<clusters::Model>>> {
|
||||
async fn get_clusters_grouped_by_major(
|
||||
self,
|
||||
) -> LauncherResult<HashMap<u32, Vec<clusters::Model>>> {
|
||||
let clusters = onelauncher_core::api::cluster::dao::get_all_clusters().await?;
|
||||
|
||||
let mut mapped: HashMap<u32, Vec<clusters::Model>> = HashMap::new();
|
||||
|
|
@ -38,13 +41,10 @@ impl OneClientApi for OneClientApiImpl {
|
|||
Err(_) => continue, // Skip if parsing fails
|
||||
};
|
||||
|
||||
mapped.entry(major)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(cluster);
|
||||
mapped.entry(major).or_default().push(cluster);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(mapped)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ async fn initialize_core() -> LauncherResult<()> {
|
|||
launcher_website: "https://polyfrost.org/".to_string(),
|
||||
discord_client_id: None, //Some(constants::DISCORD_CLIENT_ID.to_string()), // disabled for now
|
||||
fetch_attempts: 3,
|
||||
logger_filter: Some(
|
||||
format!("{}={level},onelauncher_core={level}", env!("CARGO_PKG_NAME")),
|
||||
),
|
||||
logger_filter: Some(format!(
|
||||
"{}={level},onelauncher_core={level}",
|
||||
env!("CARGO_PKG_NAME")
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
@ -68,7 +69,7 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
|||
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
// .plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
|
|
@ -98,9 +99,7 @@ async fn initialize_state(handle: &tauri::AppHandle) -> LauncherResult<()> {
|
|||
}
|
||||
|
||||
pub async fn run() {
|
||||
initialize_core()
|
||||
.await
|
||||
.expect("failed to initialize core");
|
||||
initialize_core().await.expect("failed to initialize core");
|
||||
|
||||
let app = initialize_tauri(tauri::Builder::default())
|
||||
.await
|
||||
|
|
@ -110,8 +109,7 @@ pub async fn run() {
|
|||
.await
|
||||
.expect("failed to initialize state");
|
||||
|
||||
initialize_oneclient()
|
||||
.await;
|
||||
initialize_oneclient().await;
|
||||
|
||||
app.run(|_, _| {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
use onelauncher_core::{entity::{clusters::Model as ClusterModel, icon::Icon, loader::GameLoader}, error::LauncherResult, send_error};
|
||||
use onelauncher_core::entity::clusters::Model as ClusterModel;
|
||||
use onelauncher_core::entity::icon::Icon;
|
||||
use onelauncher_core::entity::loader::GameLoader;
|
||||
use onelauncher_core::error::LauncherResult;
|
||||
use onelauncher_core::send_error;
|
||||
|
||||
pub async fn initialize_oneclient() {
|
||||
init_clusters().await;
|
||||
|
|
@ -31,12 +35,14 @@ async fn init_clusters() {
|
|||
|
||||
for loader in &cluster.mc_loaders {
|
||||
if let Err(e) = create_cluster_if_not_exist(
|
||||
&format!("{} {}", mc_version, loader),
|
||||
&format!("{mc_version} {loader}"),
|
||||
&mc_version,
|
||||
*loader,
|
||||
None,
|
||||
None,
|
||||
).await {
|
||||
)
|
||||
.await
|
||||
{
|
||||
send_error!("failed to create cluster for {}: {}", mc_version, e);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +62,13 @@ async fn create_cluster_if_not_exist(
|
|||
return Ok(None);
|
||||
}
|
||||
|
||||
onelauncher_core::api::cluster::create_cluster(name, mc_version, mc_loader, mc_loader_version, icon_url)
|
||||
.await
|
||||
.map(Some)
|
||||
}
|
||||
onelauncher_core::api::cluster::create_cluster(
|
||||
name,
|
||||
mc_version,
|
||||
mc_loader,
|
||||
mc_loader_version,
|
||||
icon_url,
|
||||
)
|
||||
.await
|
||||
.map(Some)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"createUpdaterArtifacts": false,
|
||||
"targets": [
|
||||
"app",
|
||||
"dmg",
|
||||
|
|
@ -66,7 +67,6 @@
|
|||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://polyfrost.org/",
|
||||
"longDescription": "Next-generation open source Minecraft launcher",
|
||||
"createUpdaterArtifacts": true,
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"linux": {
|
||||
|
|
|
|||
3
apps/oneclient/frontend/.gitignore
vendored
3
apps/oneclient/frontend/.gitignore
vendored
|
|
@ -4,4 +4,5 @@ dist
|
|||
dist-ssr
|
||||
*.local
|
||||
|
||||
**/*.gen.ts
|
||||
# **/*.gen.ts
|
||||
routeTree.gen.ts
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
"build": "vite build && tsc",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint",
|
||||
"format": "prettier",
|
||||
"check": "prettier --write . && eslint --fix"
|
||||
"check": "eslint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/poppins": "catalog:",
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
// @ts-check
|
||||
|
||||
/** @type {import('prettier').Config} */
|
||||
const config = {
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
|
||||
export default config;
|
||||
251
apps/oneclient/frontend/src/bindings.gen.ts
Normal file
251
apps/oneclient/frontend/src/bindings.gen.ts
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
// This file has been generated by Specta. DO NOT EDIT.
|
||||
|
||||
import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc'
|
||||
type TAURI_CHANNEL<T> = (response: T) => void
|
||||
|
||||
|
||||
export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string }
|
||||
|
||||
export type ClusterModel = { 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 ClusterStage = "notready" | "downloading" | "repairing" | "ready"
|
||||
|
||||
export type ClusterUpdate = { name: string | null; icon_url: Icon | null }
|
||||
|
||||
export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null }
|
||||
|
||||
export type CryptoError = { type: "InvalidHash"; data: string } | { type: "InvalidAlgorithm"; data: string }
|
||||
|
||||
export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExists"; data: string } | { type: "InvalidValue"; data: string }
|
||||
|
||||
export type DbVec = T[]
|
||||
|
||||
export type DirectoryError = { type: "BaseDir"; data: string }
|
||||
|
||||
export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string }
|
||||
|
||||
export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: string[] | null; package_types: PackageType[] | null }
|
||||
|
||||
export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric"
|
||||
|
||||
export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string }
|
||||
|
||||
export type Icon = string
|
||||
|
||||
export type IncompatiblePackageType = { type: "McVersion"; data: string } | { type: "Loader"; data: string }
|
||||
|
||||
export type IngressError = { type: "NotFound"; data: string }
|
||||
|
||||
export type 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 JavaError = { type: "ParseVersion"; data: string } | { type: "Execute"; data: string } | { type: "MissingJava"; data: string }
|
||||
|
||||
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"; data: string } | { type: "AnyhowError"; data: string } | { type: "DbError"; data: string } | { type: "ReqwestError"; data: string } | { type: "InterpulseError"; data: string } | { type: "RegexError"; data: string } | { type: "SemaphoreError"; data: string } | { type: "UrlError"; data: string } | { type: "TauriError"; data: string }
|
||||
|
||||
export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: string; version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: string[]; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: bigint; gallery: PackageGallery[] }
|
||||
|
||||
export type ManagedUser = { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null }
|
||||
|
||||
export type ManagedVersion = { version_id: string; project_id: string; display_name: string; display_version: string; changelog: string | null; dependencies: ManagedVersionDependency[]; mc_versions: string[]; release_type: PackageReleaseType; loaders: GameLoader[]; published: string; downloads: bigint; files: ManagedVersionFile[] }
|
||||
|
||||
export type ManagedVersionDependency = { version_id: string | null; project_id: string | null; file_name: string | null; dependency_type: PackageDependencyType }
|
||||
|
||||
export type ManagedVersionFile = { sha1: string; url: string; file_name: string; primary: boolean; size: bigint }
|
||||
|
||||
export type MessageLevel = "Info" | "Warn" | "Error"
|
||||
|
||||
export type MessagePayload = { level: MessageLevel; message: string }
|
||||
|
||||
export type MetadataError = { type: "FetchError"; data: string } | { type: "NotModdedManifest"; data: string } | { type: "NotVanillaManifest"; data: string } | { type: "ParseError"; data: string } | { type: "NoMatchingLoader"; data: string } | { type: "NoMatchingVersion"; data: string }
|
||||
|
||||
export type MinecraftAuthError = { type: "PublicKeyReading"; data: string } | { type: "PKCS8Error"; data: string } | { type: "SerializeError"; data: string } | { type: "DeserializeError"; data: string } | { type: "RequestError"; data: string } | { type: "SigningError"; data: string } | { type: "HashError"; data: string } | { type: "SessionIdError"; data: string }
|
||||
|
||||
/**
|
||||
* A structure of all needed Minecraft credentials for logging in and account management.
|
||||
*/
|
||||
export type MinecraftCredentials = {
|
||||
/**
|
||||
* The uuid of the credentials.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The username of the Minecraft account.
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* The access token as a String.
|
||||
*/
|
||||
access_token: string;
|
||||
/**
|
||||
* The refresh token as a string for [`MinecraftState#refresh`].
|
||||
*/
|
||||
refresh_token: string;
|
||||
/**
|
||||
* The time that the access token expires as a [`DateTime<Utc>`].
|
||||
*/
|
||||
expires: string }
|
||||
|
||||
export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] }
|
||||
|
||||
export type PackageDependencyType = "required" | "optional" | "embedded" | "incompatible"
|
||||
|
||||
/**
|
||||
* https://api.modrinth.com/v2/tag/donation_platform
|
||||
*/
|
||||
export type PackageDonationPlatform = "patreon" | "buymeacoffee" | "paypal" | "github" | "kofi" | "other"
|
||||
|
||||
export type PackageDonationUrl = { id: PackageDonationPlatform; url: string }
|
||||
|
||||
export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType }
|
||||
|
||||
export type PackageGallery = { url: string; thumbnail_url: string; title: string | null; description: string | null; featured: boolean | null }
|
||||
|
||||
/**
|
||||
* https://spdx.org/licenses/
|
||||
*/
|
||||
export type PackageLicense = { id: string; name: string; url: string | null }
|
||||
|
||||
/**
|
||||
* https://docs.curseforge.com/rest-api/#tocS_ModLinks
|
||||
*/
|
||||
export type PackageLinks = { website: string | null; issues: string | null; source: string | null; wiki: string | null; donation: PackageDonationUrl[] | null; discord: string | null }
|
||||
|
||||
export type PackageModel = { hash: string; file_name: string; version_id: string; published_at: string; display_name: string; display_version: string; package_type: PackageType; provider: Provider; package_id: string; mc_versions: DbVec<string>; mc_loader: DbVec<GameLoader>; icon: Icon | null }
|
||||
|
||||
export type PackageReleaseType = "release" | "beta" | "alpha"
|
||||
|
||||
export type PackageSide = "unknown" | "required" | "optional" | "unsupported"
|
||||
|
||||
export type PackageStatus = "active" | "abandoned"
|
||||
|
||||
export type PackageType = "mod" | "resourcepack" | "shader" | "datapack" | "modpack"
|
||||
|
||||
export type Paginated<T> = { total: bigint; offset: bigint; limit: bigint; items: T[] }
|
||||
|
||||
export type Process = { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
||||
|
||||
export type ProcessError = { type: "HookUnsuccessful"; data: string } | { type: "NoPid"; data: string }
|
||||
|
||||
export type ProcessPayload = { type: "Starting"; command: string } | { type: "Started"; process: Process } | { type: "Stopped"; pid: number; exit_code: number } | { type: "Output"; pid: number; output: string }
|
||||
|
||||
export type ProfileUpdate = { 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 }
|
||||
|
||||
export type Provider = "Modrinth" | "Curseforge" | "SkyClient"
|
||||
|
||||
export type Resolution = { width: number; height: number }
|
||||
|
||||
export type SearchQuery = { query: string | null; offset: bigint | null; limit: bigint | null; sort: Sort | null; filters: Filters | null }
|
||||
|
||||
export type SearchResult = { project_id: string; project_type: string; slug: string; author: string; title: string; description: string; categories: string[]; display_categories: string[]; versions: string[]; downloads: bigint; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string; client_side: PackageSide; server_side: PackageSide;
|
||||
/**
|
||||
* List of URLs to images
|
||||
*/
|
||||
gallery: string[] }
|
||||
|
||||
export type 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 type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: bigint; settings_version: number; native_window_frame: boolean }
|
||||
|
||||
export type SettingsOsExtra = { enable_gamemode: boolean | null }
|
||||
|
||||
export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated"
|
||||
|
||||
/**
|
||||
* A game version of Minecraft
|
||||
*/
|
||||
export type Version = {
|
||||
/**
|
||||
* A unique identifier of the version
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The release type of the version
|
||||
*/
|
||||
type: VersionType;
|
||||
/**
|
||||
* A link to additional information about the version
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* The latest time a file in this version was updated
|
||||
*/
|
||||
time: string;
|
||||
/**
|
||||
* The time this version was released
|
||||
*/
|
||||
releaseTime: string;
|
||||
/**
|
||||
* The SHA1 hash of the additional information about the version
|
||||
*/
|
||||
sha1: string;
|
||||
/**
|
||||
* Whether the version supports the latest player safety features
|
||||
*/
|
||||
complianceLevel: number;
|
||||
/**
|
||||
* The SHA1 hash of the original unmodified Minecraft version's JSON
|
||||
* This is only available when using the Pulseflow mirror
|
||||
*/
|
||||
originalSha1?: string | null }
|
||||
|
||||
/**
|
||||
* The version type of Minecraft
|
||||
*/
|
||||
export type VersionType =
|
||||
/**
|
||||
* A major version, which is stable for all players to use
|
||||
*/
|
||||
"release" |
|
||||
/**
|
||||
* An experimental version, which is unstable and used for feature previews and beta testing
|
||||
*/
|
||||
"snapshot" |
|
||||
/**
|
||||
* The oldest versions before the game was released
|
||||
*/
|
||||
"old_alpha" |
|
||||
/**
|
||||
* Early versions of the game
|
||||
*/
|
||||
"old_beta"
|
||||
|
||||
const ARGS_MAP = { 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'core':'{"getPackageVersions":["provider","slug","mc_versions","loaders","offset","limit"],"readSettings":[],"getDefaultUser":["fallback"],"getPackageUser":["provider","slug"],"getClusterById":["id"],"updateClusterById":["id","request"],"getClusters":[],"getLoadersForVersion":["mc_version"],"searchPackages":["provider","query"],"removeCluster":["id"],"getPackage":["provider","slug"],"launchCluster":["id","uuid"],"openMsaLogin":[],"getMultiplePackages":["provider","slugs"],"getUsersFromAuthor":["provider","author"],"getGlobalProfile":[],"removeUser":["uuid"],"getWorlds":["id"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsers":[],"getGameVersions":[],"getUser":["uuid"],"setDefaultUser":["uuid"],"updateClusterProfile":["name","profile"],"getProfileOrDefault":["name"],"createCluster":["options"],"getScreenshots":["id"],"writeSettings":["setting"]}', 'oneclient':'{"getClustersGroupedByMajor":[],"openDevTools":[]}' }
|
||||
export type Router = { 'oneclient': { openDevTools: () => Promise<void>,
|
||||
getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>> },
|
||||
'core': { getClusters: () => Promise<ClusterModel[]>,
|
||||
getClusterById: (id: bigint) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: bigint) => Promise<null>,
|
||||
createCluster: (options: CreateCluster) => Promise<ClusterModel>,
|
||||
launchCluster: (id: bigint, uuid: string | null) => Promise<null>,
|
||||
updateClusterById: (id: bigint, request: ClusterUpdate) => Promise<null>,
|
||||
getScreenshots: (id: bigint) => Promise<string[]>,
|
||||
getWorlds: (id: bigint) => Promise<string[]>,
|
||||
getProfileOrDefault: (name: string | null) => Promise<SettingProfileModel>,
|
||||
getGlobalProfile: () => Promise<SettingProfileModel>,
|
||||
updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise<SettingProfileModel>,
|
||||
getGameVersions: () => Promise<Version[]>,
|
||||
getLoadersForVersion: (mcVersion: string) => Promise<GameLoader[]>,
|
||||
getUsers: () => Promise<MinecraftCredentials[]>,
|
||||
getUser: (uuid: string) => Promise<MinecraftCredentials | null>,
|
||||
removeUser: (uuid: string) => Promise<null>,
|
||||
getDefaultUser: (fallback: boolean | null) => Promise<MinecraftCredentials | null>,
|
||||
setDefaultUser: (uuid: string | null) => Promise<null>,
|
||||
openMsaLogin: () => Promise<MinecraftCredentials | null>,
|
||||
readSettings: () => Promise<Settings>,
|
||||
writeSettings: (setting: Settings) => Promise<null>,
|
||||
searchPackages: (provider: Provider, query: SearchQuery) => Promise<Paginated<SearchResult>>,
|
||||
getPackage: (provider: Provider, slug: string) => Promise<ManagedPackage>,
|
||||
getMultiplePackages: (provider: Provider, slugs: string[]) => Promise<ManagedPackage[]>,
|
||||
getPackageVersions: (provider: Provider, slug: string, mcVersions: string[] | null, loaders: GameLoader[] | null, offset: bigint, limit: bigint) => Promise<Paginated<ManagedVersion>>,
|
||||
getPackageUser: (provider: Provider, slug: string) => Promise<ManagedUser>,
|
||||
downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: bigint, skipCompatibility: boolean | null) => Promise<PackageModel>,
|
||||
getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<ManagedUser[]> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> } };
|
||||
|
||||
|
||||
export type { InferCommandOutput }
|
||||
export const createTauRPCProxy = () => createProxy<Router>(ARGS_MAP)
|
||||
|
|
@ -25,5 +25,5 @@
|
|||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "prettier.config.js", "vite.config.js"]
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use onelauncher_core::api::proxy::ProxyTauri;
|
||||
use onelauncher_core::api::tauri::{TauriLauncherEventApi, TauriLauncherApi};
|
||||
use onelauncher_core::api::tauri::{TauriLauncherApi, TauriLauncherEventApi};
|
||||
use onelauncher_core::error::LauncherResult;
|
||||
use onelauncher_core::store::proxy::ProxyState;
|
||||
use onelauncher_core::store::semaphore::SemaphoreStore;
|
||||
|
|
@ -33,9 +33,10 @@ async fn initialize_core() -> LauncherResult<()> {
|
|||
launcher_website: "https://polyfrost.org/".to_string(),
|
||||
discord_client_id: Some(constants::DISCORD_CLIENT_ID.to_string()),
|
||||
fetch_attempts: 3,
|
||||
logger_filter: Some(
|
||||
format!("{}={level},onelauncher_core={level}", env!("CARGO_PKG_NAME")),
|
||||
),
|
||||
logger_filter: Some(format!(
|
||||
"{}={level},onelauncher_core={level}",
|
||||
env!("CARGO_PKG_NAME")
|
||||
)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
@ -61,13 +62,14 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
|||
.merge(onelauncher_core::api::tauri::TauriLauncherApiImpl.into_handler())
|
||||
.merge(onelauncher_core::api::tauri::TauriLauncherEventApiImpl.into_handler());
|
||||
|
||||
let launcher_path = Dirs::get().await?.base_dir();
|
||||
let builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
println!("{}, {argv:?}, {cwd}", app.package_info().name);
|
||||
app.emit("single-instance", SingleInstancePayload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||
// .plugin(tauri_plugin_updater::Builder::new().build())
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
|
|
@ -75,10 +77,8 @@ async fn initialize_tauri(builder: tauri::Builder<tauri::Wry>) -> LauncherResult
|
|||
.menu(tauri::menu::Menu::new)
|
||||
.invoke_handler(router.into_handler())
|
||||
.setup(move |app| {
|
||||
let launcher_path = app.path().data_dir().ok().expect("ok").join("OneLauncher");
|
||||
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(&launcher_path, true)
|
||||
.allow_directory(launcher_path, true)
|
||||
.unwrap();
|
||||
|
||||
setup_window(app.handle()).expect("failed to setup main window");
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"createUpdaterArtifacts": false,
|
||||
"targets": [
|
||||
"app",
|
||||
"dmg",
|
||||
|
|
@ -66,7 +67,6 @@
|
|||
"license": "GPL-3.0-only",
|
||||
"homepage": "https://polyfrost.org/",
|
||||
"longDescription": "Next-generation open source Minecraft launcher",
|
||||
"createUpdaterArtifacts": true,
|
||||
"resources": [],
|
||||
"externalBin": [],
|
||||
"linux": {
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "",
|
||||
"active": false,
|
||||
"endpoints": ["https://polyfrost.org/releases/tauri/{{current_version}}/{{target}}/{{arch}}"]
|
||||
},
|
||||
"deep-link": {
|
||||
|
|
|
|||
3
apps/onelauncher/frontend/.gitignore
vendored
3
apps/onelauncher/frontend/.gitignore
vendored
|
|
@ -4,4 +4,5 @@ dist
|
|||
dist-ssr
|
||||
*.local
|
||||
|
||||
**/*.gen.ts
|
||||
# **/*.gen.ts
|
||||
routeTree.gen.ts
|
||||
|
|
|
|||
253
apps/onelauncher/frontend/src/bindings.gen.ts
Normal file
253
apps/onelauncher/frontend/src/bindings.gen.ts
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
// @ts-nocheck
|
||||
// This file has been generated by Specta. DO NOT EDIT.
|
||||
|
||||
import { createTauRPCProxy as createProxy, type InferCommandOutput } from 'taurpc'
|
||||
type TAURI_CHANNEL<T> = (response: T) => void
|
||||
|
||||
|
||||
export type ClusterError = { type: "InvalidVersion"; data: string } | { type: "MissingJavaVersion"; data: string } | { type: "ClusterDownloading"; data: string } | { type: "ClusterAlreadyRunning"; data: string }
|
||||
|
||||
export type ClusterModel = { 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 ClusterStage = "notready" | "downloading" | "repairing" | "ready"
|
||||
|
||||
export type ClusterUpdate = { name: string | null; icon_url: Icon | null }
|
||||
|
||||
export type CreateCluster = { name: string; mc_version: string; mc_loader: GameLoader; mc_loader_version: string | null; icon: Icon | null }
|
||||
|
||||
export type CryptoError = { type: "InvalidHash"; data: string } | { type: "InvalidAlgorithm"; data: string }
|
||||
|
||||
export type DaoError = { type: "NotFound"; data: string } | { type: "AlreadyExists"; data: string } | { type: "InvalidValue"; data: string }
|
||||
|
||||
export type DbVec = T[]
|
||||
|
||||
export type DirectoryError = { type: "BaseDir"; data: string }
|
||||
|
||||
export type DiscordError = { type: "MissingClientId"; data: string } | { type: "ConnectError"; data: string }
|
||||
|
||||
export type Filters = { game_versions: string[] | null; loaders: GameLoader[] | null; categories: string[] | null; package_types: PackageType[] | null }
|
||||
|
||||
export type GameLoader = "vanilla" | "forge" | "neoforge" | "quilt" | "fabric" | "legacyfabric"
|
||||
|
||||
export type IOError = { type: "InvalidAbsolutePath"; data: string } | { type: "IOErrorWrapper"; data: string } | { type: "IOError"; data: string } | { type: "DeserializeError"; data: string } | { type: "AsyncZipError"; data: string }
|
||||
|
||||
export type Icon = string
|
||||
|
||||
export type IncompatiblePackageType = { type: "McVersion"; data: string } | { type: "Loader"; data: string }
|
||||
|
||||
export type IngressError = { type: "NotFound"; data: string }
|
||||
|
||||
export type 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 JavaError = { type: "ParseVersion"; data: string } | { type: "Execute"; data: string } | { type: "MissingJava"; data: string }
|
||||
|
||||
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"; data: string } | { type: "AnyhowError"; data: string } | { type: "DbError"; data: string } | { type: "ReqwestError"; data: string } | { type: "InterpulseError"; data: string } | { type: "RegexError"; data: string } | { type: "SemaphoreError"; data: string } | { type: "UrlError"; data: string } | { type: "TauriError"; data: string }
|
||||
|
||||
export type ManagedPackage = { id: string; slug: string; provider: Provider; package_type: PackageType; name: string; short_desc: string; body: string; version_ids: string[]; mc_versions: string[]; loaders: GameLoader[]; icon_url: string | null; created: string; updated: string; client: PackageSide; server: PackageSide; categories: string[]; license: PackageLicense | null; author: PackageAuthor; links: PackageLinks; status: PackageStatus; downloads: bigint; gallery: PackageGallery[] }
|
||||
|
||||
export type ManagedUser = { id: string; username: string; url?: string | null; avatar_url?: string | null; bio?: string | null; is_organization_user?: boolean; role?: string | null }
|
||||
|
||||
export type ManagedVersion = { version_id: string; project_id: string; display_name: string; display_version: string; changelog: string | null; dependencies: ManagedVersionDependency[]; mc_versions: string[]; release_type: PackageReleaseType; loaders: GameLoader[]; published: string; downloads: bigint; files: ManagedVersionFile[] }
|
||||
|
||||
export type ManagedVersionDependency = { version_id: string | null; project_id: string | null; file_name: string | null; dependency_type: PackageDependencyType }
|
||||
|
||||
export type ManagedVersionFile = { sha1: string; url: string; file_name: string; primary: boolean; size: bigint }
|
||||
|
||||
export type MessageLevel = "Info" | "Warn" | "Error"
|
||||
|
||||
export type MessagePayload = { level: MessageLevel; message: string }
|
||||
|
||||
export type MetadataError = { type: "FetchError"; data: string } | { type: "NotModdedManifest"; data: string } | { type: "NotVanillaManifest"; data: string } | { type: "ParseError"; data: string } | { type: "NoMatchingLoader"; data: string } | { type: "NoMatchingVersion"; data: string }
|
||||
|
||||
export type MinecraftAuthError = { type: "PublicKeyReading"; data: string } | { type: "PKCS8Error"; data: string } | { type: "SerializeError"; data: string } | { type: "DeserializeError"; data: string } | { type: "RequestError"; data: string } | { type: "SigningError"; data: string } | { type: "HashError"; data: string } | { type: "SessionIdError"; data: string }
|
||||
|
||||
/**
|
||||
* A structure of all needed Minecraft credentials for logging in and account management.
|
||||
*/
|
||||
export type MinecraftCredentials = {
|
||||
/**
|
||||
* The uuid of the credentials.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The username of the Minecraft account.
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* The access token as a String.
|
||||
*/
|
||||
access_token: string;
|
||||
/**
|
||||
* The refresh token as a string for [`MinecraftState#refresh`].
|
||||
*/
|
||||
refresh_token: string;
|
||||
/**
|
||||
* The time that the access token expires as a [`DateTime<Utc>`].
|
||||
*/
|
||||
expires: string }
|
||||
|
||||
export type PackageAuthor = { Team: { team_id: string; org_id: string | null } } | { Users: ManagedUser[] }
|
||||
|
||||
export type PackageDependencyType = "required" | "optional" | "embedded" | "incompatible"
|
||||
|
||||
/**
|
||||
* https://api.modrinth.com/v2/tag/donation_platform
|
||||
*/
|
||||
export type PackageDonationPlatform = "patreon" | "buymeacoffee" | "paypal" | "github" | "kofi" | "other"
|
||||
|
||||
export type PackageDonationUrl = { id: PackageDonationPlatform; url: string }
|
||||
|
||||
export type PackageError = { type: "NoPrimaryFile"; data: string } | { type: "IsModPack"; data: string } | { type: "Incompatible"; data: IncompatiblePackageType }
|
||||
|
||||
export type PackageGallery = { url: string; thumbnail_url: string; title: string | null; description: string | null; featured: boolean | null }
|
||||
|
||||
/**
|
||||
* https://spdx.org/licenses/
|
||||
*/
|
||||
export type PackageLicense = { id: string; name: string; url: string | null }
|
||||
|
||||
/**
|
||||
* https://docs.curseforge.com/rest-api/#tocS_ModLinks
|
||||
*/
|
||||
export type PackageLinks = { website: string | null; issues: string | null; source: string | null; wiki: string | null; donation: PackageDonationUrl[] | null; discord: string | null }
|
||||
|
||||
export type PackageModel = { hash: string; file_name: string; version_id: string; published_at: string; display_name: string; display_version: string; package_type: PackageType; provider: Provider; package_id: string; mc_versions: DbVec<string>; mc_loader: DbVec<GameLoader>; icon: Icon | null }
|
||||
|
||||
export type PackageReleaseType = "release" | "beta" | "alpha"
|
||||
|
||||
export type PackageSide = "unknown" | "required" | "optional" | "unsupported"
|
||||
|
||||
export type PackageStatus = "active" | "abandoned"
|
||||
|
||||
export type PackageType = "mod" | "resourcepack" | "shader" | "datapack" | "modpack"
|
||||
|
||||
export type Paginated<T> = { total: bigint; offset: bigint; limit: bigint; items: T[] }
|
||||
|
||||
export type Process = { pid: number; started_at: string; cluster_id: bigint; post_hook: string | null; account_id: string }
|
||||
|
||||
export type ProcessError = { type: "HookUnsuccessful"; data: string } | { type: "NoPid"; data: string }
|
||||
|
||||
export type ProcessPayload = { type: "Starting"; command: string } | { type: "Started"; process: Process } | { type: "Stopped"; pid: number; exit_code: number } | { type: "Output"; pid: number; output: string }
|
||||
|
||||
export type ProfileUpdate = { 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 }
|
||||
|
||||
export type Provider = "Modrinth" | "Curseforge" | "SkyClient"
|
||||
|
||||
export type Resolution = { width: number; height: number }
|
||||
|
||||
export type SearchQuery = { query: string | null; offset: bigint | null; limit: bigint | null; sort: Sort | null; filters: Filters | null }
|
||||
|
||||
export type SearchResult = { project_id: string; project_type: string; slug: string; author: string; title: string; description: string; categories: string[]; display_categories: string[]; versions: string[]; downloads: bigint; icon_url: string; date_created: string; date_modified: string; latest_version: string; license: string; client_side: PackageSide; server_side: PackageSide;
|
||||
/**
|
||||
* List of URLs to images
|
||||
*/
|
||||
gallery: string[] }
|
||||
|
||||
export type 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 type Settings = { global_game_settings: SettingProfileModel; allow_parallel_running_clusters: boolean; enable_gamemode: boolean; discord_enabled: boolean; max_concurrent_requests: bigint; settings_version: number; native_window_frame: boolean }
|
||||
|
||||
export type SettingsOsExtra = Record<string, never>
|
||||
|
||||
export type Sort = "Relevance" | "Downloads" | "Newest" | "Updated"
|
||||
|
||||
/**
|
||||
* A game version of Minecraft
|
||||
*/
|
||||
export type Version = {
|
||||
/**
|
||||
* A unique identifier of the version
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The release type of the version
|
||||
*/
|
||||
type: VersionType;
|
||||
/**
|
||||
* A link to additional information about the version
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* The latest time a file in this version was updated
|
||||
*/
|
||||
time: string;
|
||||
/**
|
||||
* The time this version was released
|
||||
*/
|
||||
releaseTime: string;
|
||||
/**
|
||||
* The SHA1 hash of the additional information about the version
|
||||
*/
|
||||
sha1: string;
|
||||
/**
|
||||
* Whether the version supports the latest player safety features
|
||||
*/
|
||||
complianceLevel: number;
|
||||
/**
|
||||
* The SHA1 hash of the original unmodified Minecraft version's JSON
|
||||
* This is only available when using the Pulseflow mirror
|
||||
*/
|
||||
originalSha1?: string | null }
|
||||
|
||||
/**
|
||||
* The version type of Minecraft
|
||||
*/
|
||||
export type VersionType =
|
||||
/**
|
||||
* A major version, which is stable for all players to use
|
||||
*/
|
||||
"release" |
|
||||
/**
|
||||
* An experimental version, which is unstable and used for feature previews and beta testing
|
||||
*/
|
||||
"snapshot" |
|
||||
/**
|
||||
* The oldest versions before the game was released
|
||||
*/
|
||||
"old_alpha" |
|
||||
/**
|
||||
* Early versions of the game
|
||||
*/
|
||||
"old_beta"
|
||||
|
||||
const ARGS_MAP = { 'events':'{"ingress":["event"],"message":["event"],"process":["event"]}', 'core':'{"removeUser":["uuid"],"writeSettings":["setting"],"updateClusterById":["id","request"],"getScreenshots":["id"],"getClusters":[],"getWorlds":["id"],"getGameVersions":[],"getPackage":["provider","slug"],"getLoadersForVersion":["mc_version"],"getMultiplePackages":["provider","slugs"],"getPackageUser":["provider","slug"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"getUsersFromAuthor":["provider","author"],"searchPackages":["provider","query"],"getUser":["uuid"],"getProfileOrDefault":["name"],"updateClusterProfile":["name","profile"],"getPackageVersions":["provider","slug","mc_versions","loaders","offset","limit"],"launchCluster":["id","uuid"],"setDefaultUser":["uuid"],"openMsaLogin":[],"getGlobalProfile":[],"getClusterById":["id"],"getDefaultUser":["fallback"],"readSettings":[],"removeCluster":["id"],"createCluster":["options"],"getUsers":[]}', 'onelauncher':'{"set_window_style":["decorations"],"return_error":[],"open_dev_tools":[]}' }
|
||||
export type Router = { 'core': { getClusters: () => Promise<ClusterModel[]>,
|
||||
getClusterById: (id: bigint) => Promise<ClusterModel | null>,
|
||||
removeCluster: (id: bigint) => Promise<null>,
|
||||
createCluster: (options: CreateCluster) => Promise<ClusterModel>,
|
||||
launchCluster: (id: bigint, uuid: string | null) => Promise<null>,
|
||||
updateClusterById: (id: bigint, request: ClusterUpdate) => Promise<null>,
|
||||
getScreenshots: (id: bigint) => Promise<string[]>,
|
||||
getWorlds: (id: bigint) => Promise<string[]>,
|
||||
getProfileOrDefault: (name: string | null) => Promise<SettingProfileModel>,
|
||||
getGlobalProfile: () => Promise<SettingProfileModel>,
|
||||
updateClusterProfile: (name: string, profile: ProfileUpdate) => Promise<SettingProfileModel>,
|
||||
getGameVersions: () => Promise<Version[]>,
|
||||
getLoadersForVersion: (mcVersion: string) => Promise<GameLoader[]>,
|
||||
getUsers: () => Promise<MinecraftCredentials[]>,
|
||||
getUser: (uuid: string) => Promise<MinecraftCredentials | null>,
|
||||
removeUser: (uuid: string) => Promise<null>,
|
||||
getDefaultUser: (fallback: boolean | null) => Promise<MinecraftCredentials | null>,
|
||||
setDefaultUser: (uuid: string | null) => Promise<null>,
|
||||
openMsaLogin: () => Promise<MinecraftCredentials | null>,
|
||||
readSettings: () => Promise<Settings>,
|
||||
writeSettings: (setting: Settings) => Promise<null>,
|
||||
searchPackages: (provider: Provider, query: SearchQuery) => Promise<Paginated<SearchResult>>,
|
||||
getPackage: (provider: Provider, slug: string) => Promise<ManagedPackage>,
|
||||
getMultiplePackages: (provider: Provider, slugs: string[]) => Promise<ManagedPackage[]>,
|
||||
getPackageVersions: (provider: Provider, slug: string, mcVersions: string[] | null, loaders: GameLoader[] | null, offset: bigint, limit: bigint) => Promise<Paginated<ManagedVersion>>,
|
||||
getPackageUser: (provider: Provider, slug: string) => Promise<ManagedUser>,
|
||||
downloadPackage: (provider: Provider, packageId: string, versionId: string, clusterId: bigint, skipCompatibility: boolean | null) => Promise<PackageModel>,
|
||||
getUsersFromAuthor: (provider: Provider, author: PackageAuthor) => Promise<ManagedUser[]> },
|
||||
'events': { ingress: (event: IngressPayload) => Promise<void>,
|
||||
message: (event: MessagePayload) => Promise<void>,
|
||||
process: (event: ProcessPayload) => Promise<void> },
|
||||
'onelauncher': { return_error: () => Promise<null>,
|
||||
open_dev_tools: () => Promise<void>,
|
||||
set_window_style: (decorations: boolean) => Promise<void> } };
|
||||
|
||||
|
||||
export type { InferCommandOutput }
|
||||
export const createTauRPCProxy = () => createProxy<Router>(ARGS_MAP)
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Provider, SearchResult } from '@/bindings.gen';
|
||||
import { abbreviateNumber, LOADERS, upperFirst } from '@/utils';
|
||||
import { Show, Tooltip } from '@onelauncher/common/components';
|
||||
import { Link, useNavigate } from '@tanstack/react-router';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
import { Download01Icon } from '@untitled-theme/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { Focusable } from 'react-aria-components';
|
||||
|
|
@ -13,7 +13,7 @@ function includes<T, TArray extends T>(list: { includes: (arg0: TArray) => boole
|
|||
|
||||
export function PackageGrid({ items, provider }: { items: Array<SearchResult>; provider: Provider }) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-2">
|
||||
<div className="grid grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 min-[1900px]:grid-cols-5! gap-2">
|
||||
{items.map(item => (
|
||||
<PackageItem key={item.project_id} {...item} provider={provider} />
|
||||
))}
|
||||
|
|
@ -22,7 +22,7 @@ export function PackageGrid({ items, provider }: { items: Array<SearchResult>; p
|
|||
}
|
||||
|
||||
export function PackageItem({ provider, ...item }: SearchResult & { provider: Provider }) {
|
||||
const loaders = useMemo(() => item.categories.filter(cat => includes(LOADERS, cat)), []);
|
||||
const loaders = useMemo(() => item.categories.filter(cat => includes(LOADERS, cat)), [item.categories]);
|
||||
|
||||
return (
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
import type { ClusterModel, GameLoader, ManagedPackage, ManagedVersion, Paginated, Provider, SearchQuery, SearchResult } from '@/bindings.gen';
|
||||
import type { BindingCommands } from '@/types/global';
|
||||
import type { UndefinedInitialDataOptions } from '@tanstack/react-query';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { bindings } from '@/main';
|
||||
import { PROVIDERS } from '@/utils';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { useCommand, useCommandMut } from '@onelauncher/common';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export interface BrowserControllerType {
|
||||
cluster: ClusterModel | undefined;
|
||||
setCluster: (cluster: ClusterModel | undefined) => void;
|
||||
setCluster: Dispatch<SetStateAction<ClusterModel | undefined>>;
|
||||
provider: Provider;
|
||||
setProvider: (provider: Provider) => void;
|
||||
setProvider: Dispatch<SetStateAction<Provider>>;
|
||||
query: SearchQuery;
|
||||
setQuery: (query: SearchQuery) => void;
|
||||
setQuery: Dispatch<SetStateAction<SearchQuery>>;
|
||||
search: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ export function BrowserProvider(props: any) {
|
|||
categories: null,
|
||||
game_versions: null,
|
||||
loaders: null,
|
||||
package_types: ["mod"]
|
||||
package_types: ['mod'],
|
||||
},
|
||||
query: null,
|
||||
limit: 18 as unknown as bigint,
|
||||
|
|
@ -47,7 +48,7 @@ export function BrowserProvider(props: any) {
|
|||
useEffect(() => {
|
||||
if (query.filters?.categories || query.query)
|
||||
navigate({ to: '/app/browser/search' });
|
||||
}, [query]);
|
||||
}, [navigate, query]);
|
||||
|
||||
const context = useMemo<BrowserControllerType>(() => ({
|
||||
cluster,
|
||||
|
|
@ -78,3 +79,7 @@ export function usePackageData(provider: Provider, slug: string, options?: Omit<
|
|||
export function usePackageVersions(provider: Provider, slug: string, { mc_versions, loaders, offset, limit, ...options }: { mc_versions?: Array<string> | null; loaders?: Array<GameLoader> | null; offset?: number; limit: number } & Omit<UndefinedInitialDataOptions<Paginated<ManagedVersion>>, 'queryKey' | 'queryFn'>, key: false | BindingCommands | (string & {}) = `getPackageVersions.${provider}.${slug}.${mc_versions?.join('')}.${loaders?.join('')}`) {
|
||||
return useCommand(key, () => bindings.core.getPackageVersions(provider, slug, mc_versions ?? null, loaders ?? null, (offset ?? 0) as unknown as bigint, limit as unknown as bigint), options);
|
||||
}
|
||||
|
||||
export function useDownloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) {
|
||||
return useCommandMut(() => bindings.core.downloadPackage(provider, version.project_id, version.version_id, cluster.id, skipCompatibility));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { ClusterModel } from '@/bindings.gen';
|
||||
import type { ModalProps } from '@/components/overlay/Modal';
|
||||
import Modal from '@/components/overlay/Modal';
|
||||
import { bindings } from '@/main';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import { useEffect, useState } from 'react';
|
||||
import Modal from '@/components/overlay/Modal';
|
||||
import { Button, Dropdown } from '@onelauncher/common/components';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Heading } from 'react-aria-components';
|
||||
|
||||
export function useRecentCluster() {
|
||||
|
|
@ -58,26 +58,29 @@ export function useClusters() {
|
|||
}
|
||||
|
||||
type ChooseClusterModalProps = ModalProps & {
|
||||
clusters?: ClusterModel[];
|
||||
clusters?: Array<ClusterModel>;
|
||||
confirmText?: string;
|
||||
selected?: (clusters: ClusterModel[]) => number;
|
||||
selected?: (clusters: Array<ClusterModel>) => number;
|
||||
onSelected?: (cluster: ClusterModel) => void;
|
||||
};
|
||||
|
||||
|
||||
export function ChooseClusterModal({clusters: clusterList, selected: defaultSelected, onSelected, confirmText, ...props}: ChooseClusterModalProps) {
|
||||
const clusters = useClusters()
|
||||
export function ChooseClusterModal({ clusters: clusterList, selected: defaultSelected, onSelected, confirmText, ...props }: ChooseClusterModalProps) {
|
||||
const clusters = useClusters();
|
||||
const [selected, setSelected] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clusters) return;
|
||||
const newSelected = defaultSelected?.(clusters)
|
||||
if (newSelected) setSelected(newSelected)
|
||||
}, [clusters])
|
||||
if (!clusters)
|
||||
return;
|
||||
|
||||
const newSelected = defaultSelected?.(clusters);
|
||||
|
||||
if (newSelected)
|
||||
setSelected(newSelected);
|
||||
}, [clusters, defaultSelected]);
|
||||
|
||||
function chooseCluster() {
|
||||
if (clusters !== undefined && selected !== undefined) {
|
||||
const cluster = clusters[selected];
|
||||
if (clusters !== undefined) {
|
||||
const cluster = clusters[selected] as ClusterModel | undefined;
|
||||
if (cluster !== undefined)
|
||||
onSelected?.(cluster);
|
||||
}
|
||||
|
|
@ -87,35 +90,35 @@ export function ChooseClusterModal({clusters: clusterList, selected: defaultSele
|
|||
<Modal {...props}>
|
||||
<div className="min-w-sm flex flex-col rounded-lg bg-page text-center p-4 gap-2">
|
||||
|
||||
<Heading slot='title' className='text-xl font-semibold'>Select a Cluster</Heading>
|
||||
<Dropdown
|
||||
onSelectionChange={(index) => {
|
||||
setSelected(index as number)
|
||||
}}
|
||||
selectedKey={selected}
|
||||
>
|
||||
{clusters?.map((cluster, index) => (
|
||||
<Dropdown.Item id={index} key={cluster.id}>
|
||||
{cluster.name}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
<Heading className="text-xl font-semibold" slot="title">Select a Cluster</Heading>
|
||||
<Dropdown
|
||||
onSelectionChange={(index) => {
|
||||
setSelected(index as number);
|
||||
}}
|
||||
selectedKey={selected}
|
||||
>
|
||||
{clusters?.map((cluster, index) => (
|
||||
<Dropdown.Item id={index} key={cluster.id}>
|
||||
{cluster.name}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
color="secondary"
|
||||
children="Close"
|
||||
slot="close"
|
||||
/>
|
||||
<Button
|
||||
children={confirmText ?? "Save"}
|
||||
slot="close"
|
||||
onClick={chooseCluster}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
children="Close"
|
||||
color="secondary"
|
||||
slot="close"
|
||||
/>
|
||||
<Button
|
||||
children={confirmText ?? 'Save'}
|
||||
onClick={chooseCluster}
|
||||
slot="close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
);
|
||||
|
||||
// return (
|
||||
// <Modal
|
||||
|
|
|
|||
|
|
@ -46,20 +46,20 @@ function usePagination(options: PaginationOptions) {
|
|||
return pages;
|
||||
}
|
||||
|
||||
function PaginationBtn(props: { page: number }) {
|
||||
function PaginationBtn({ page: pageIndex }: { page: number }) {
|
||||
return (
|
||||
<Button
|
||||
className="p-2 text-lg min-w-8"
|
||||
color={props.page === page ? 'primary' : 'secondary'}
|
||||
color={pageIndex === page ? 'primary' : 'secondary'}
|
||||
|
||||
onClick={() => {
|
||||
if (props.page === page)
|
||||
if (pageIndex === page)
|
||||
return;
|
||||
|
||||
setPage(props.page);
|
||||
setPage(pageIndex);
|
||||
}}
|
||||
>
|
||||
<span className="text-trim">{props.page}</span>
|
||||
<span className="text-trim">{pageIndex}</span>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import type { Settings } from '@/bindings.gen';
|
||||
import { bindings } from '@/main';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
interface SettingsControllerType {
|
||||
settings: Settings | undefined;
|
||||
|
|
@ -12,7 +12,7 @@ interface SettingsControllerType {
|
|||
setSettingsToSave: (settings: Partial<Settings> | null) => void;
|
||||
settingsChanged: boolean;
|
||||
setSettingsChanged: (changed: boolean) => void;
|
||||
createSetting: <K extends keyof Settings, V = Settings[K]>(name: K, value: V) => [V, (value: V) => void];
|
||||
createSetting: <K extends keyof Settings, V = Settings[K]>(name: K, value: V) => [V, (v: V) => void];
|
||||
}
|
||||
|
||||
const SettingsContext = createContext<SettingsControllerType | null>(null);
|
||||
|
|
@ -51,14 +51,14 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
|
|||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, []);
|
||||
|
||||
const saveChangedSettings = () => {
|
||||
const saveChangedSettings = useCallback(() => {
|
||||
if (!settings)
|
||||
return;
|
||||
|
||||
writeQuery.refetch();
|
||||
|
||||
setSettingsChanged(false);
|
||||
};
|
||||
}, [settings, writeQuery]);
|
||||
|
||||
const handleSetSettingsToSave = useCallback((newSettings: Partial<Settings> | null) => {
|
||||
setSettingsToSave(newSettings);
|
||||
|
|
@ -86,7 +86,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
|
|||
return [value, setValue];
|
||||
}, [settings]);
|
||||
|
||||
const controller: SettingsControllerType = {
|
||||
const controller: SettingsControllerType = useMemo(() => ({
|
||||
settings,
|
||||
saveChangedSettings,
|
||||
settingsToSave,
|
||||
|
|
@ -94,7 +94,7 @@ export function SettingsProvider({ children }: SettingsProviderProps) {
|
|||
settingsChanged,
|
||||
setSettingsChanged,
|
||||
createSetting,
|
||||
};
|
||||
}), [createSetting, handleSetSettingsToSave, saveChangedSettings, settings, settingsChanged, settingsToSave]);
|
||||
|
||||
if (settings === undefined)
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ function Featured() {
|
|||
const navigate = useNavigate();
|
||||
const featuredImageIndex = useMemo(() => featuredPackage.data?.gallery.findIndex(image => image.featured), [featuredPackage.data]);
|
||||
const [selectedImage, setSelectedImage] = useState(featuredImageIndex ?? 0);
|
||||
|
||||
return (
|
||||
<Show when={featuredPackage.isSuccess}>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
|
|
@ -40,7 +41,7 @@ function Featured() {
|
|||
<div className="w-full p-1">
|
||||
<img alt="thumbnail" className="aspect-video h-full w-full rounded-md object-cover object-center" src={featuredPackage.data?.gallery[selectedImage].url} />
|
||||
</div>
|
||||
<div className="max-w-64 min-w-52 flex flex-col gap-y-1 p-4">
|
||||
<div className="max-w-64 min-[1900px]:max-w-96 min-w-52 flex flex-col gap-y-1 p-4">
|
||||
<h2 className="text-[1.5rem] font-semibold">{featuredPackage.data?.name}</h2>
|
||||
|
||||
<Show when={false}>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import type { ClusterModel, ManagedPackage, ManagedUser, ManagedVersion, PackageDonationUrl, Provider } from '@/bindings.gen';
|
||||
import type { HTMLProps } from 'react';
|
||||
import Modal from '@/components/overlay/Modal';
|
||||
import { useBrowserContext, usePackageData, usePackageVersions } from '@/hooks/useBrowser';
|
||||
import { useBrowserContext, useDownloadPackage, usePackageData, usePackageVersions } from '@/hooks/useBrowser';
|
||||
import { ChooseClusterModal, useClusters } from '@/hooks/useCluster';
|
||||
import usePagination from '@/hooks/usePagination';
|
||||
import { bindings } from '@/main';
|
||||
import { abbreviateNumber, formatAsRelative, PROVIDERS, upperFirst } from '@/utils';
|
||||
import { useCommand } from '@onelauncher/common';
|
||||
|
|
@ -16,7 +17,6 @@ import Markdown from 'react-markdown';
|
|||
import rehypeRaw from 'rehype-raw';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import usePagination from '@/hooks/usePagination';
|
||||
|
||||
export const Route = createFileRoute('/app/browser/package/$provider/$slug')({
|
||||
component: RouteComponent,
|
||||
|
|
@ -26,10 +26,10 @@ function includes<T, TArray extends T>(list: { includes: (arg0: TArray) => boole
|
|||
return list.includes(element as unknown as TArray);
|
||||
}
|
||||
|
||||
function CustomA({ href, children, includeIcon, className, ...rest }: { href: string; children: any, includeIcon?: boolean } & HTMLProps<HTMLAnchorElement>) {
|
||||
function CustomA({ href, children, includeIcon, className, ...rest }: { href: string; children: any; includeIcon?: boolean } & HTMLProps<HTMLAnchorElement>) {
|
||||
return (
|
||||
<a
|
||||
className={twMerge("text-fg-primary underline", className)}
|
||||
className={twMerge('text-fg-primary underline', className)}
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -38,7 +38,7 @@ function CustomA({ href, children, includeIcon, className, ...rest }: { href: st
|
|||
{...rest}
|
||||
>
|
||||
{children}
|
||||
{includeIcon && <LinkExternal01Icon className='inline w-4 ml-1' />}
|
||||
{includeIcon && <LinkExternal01Icon className="inline w-4 ml-1" />}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
|
@ -56,10 +56,12 @@ function RouteComponent() {
|
|||
if (!includes(PROVIDERS, provider))
|
||||
throw new Error('Invalid provider');
|
||||
const packageData = usePackageData(provider, slug, {});
|
||||
const browserContext = useBrowserContext();
|
||||
const packageContextValue = useMemo(() => ({
|
||||
pkg: packageData.data,
|
||||
}), [packageData.data]);
|
||||
|
||||
return (
|
||||
<PackageContext value={{ pkg: packageData.data }}>
|
||||
<PackageContext value={packageContextValue}>
|
||||
<Show when={packageData.isSuccess && !packageData.isFetching}>
|
||||
<div className="h-full flex flex-1 flex-row items-start gap-x-4">
|
||||
<BrowserSidebar package={packageData.data!} />
|
||||
|
|
@ -84,14 +86,14 @@ function RouteComponent() {
|
|||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel id="versions">
|
||||
<Versions/>
|
||||
<Versions />
|
||||
</TabPanel>
|
||||
<TabPanel className="flex flex-wrap gap-2 justify-center" id="gallery">
|
||||
|
||||
{packageData.data?.gallery.map(item => (
|
||||
<Modal.Trigger key={item.url}>
|
||||
<Pressable>
|
||||
<div className="rounded-md overflow-hidden bg-component-bg-pressed outline-0 h-64 flex flex-col relative" aria-label='Loaders'>
|
||||
<div aria-label="Loaders" className="rounded-md overflow-hidden bg-component-bg-pressed outline-0 h-64 flex flex-col relative">
|
||||
<img className="h-full" src={item.thumbnail_url} />
|
||||
{item.title && <div className="absolute w-full bottom-0 bg-component-bg-disabled/80 p-2">{item.title}</div>}
|
||||
</div>
|
||||
|
|
@ -113,17 +115,17 @@ function RouteComponent() {
|
|||
|
||||
function getPackageUrl(pkg: ManagedPackage): string {
|
||||
switch (pkg.provider) {
|
||||
case 'Modrinth': return `https://modrinth.com/project/${pkg.slug}`
|
||||
case 'Curseforge': return `https://www.curseforge.com/minecraft/${pkg.package_type}s/${pkg.slug}`
|
||||
case 'SkyClient': return ``
|
||||
case 'Modrinth': return `https://modrinth.com/project/${pkg.slug}`;
|
||||
case 'Curseforge': return `https://www.curseforge.com/minecraft/${pkg.package_type}s/${pkg.slug}`;
|
||||
case 'SkyClient': return ``;
|
||||
}
|
||||
}
|
||||
|
||||
function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) {
|
||||
const { provider } = Route.useParams();
|
||||
|
||||
const createdAt = useMemo(() => pkg.created ? new Date(pkg.created) : null, []);
|
||||
const updatedAt = useMemo(() => pkg.updated ? new Date(pkg.updated) : null, []);
|
||||
const createdAt = useMemo(() => pkg.created ? new Date(pkg.created) : null, [pkg.created]);
|
||||
const updatedAt = useMemo(() => pkg.updated ? new Date(pkg.updated) : null, [pkg.updated]);
|
||||
|
||||
const authors = useCommand('getUsersFromAuthor', () => bindings.core.getUsersFromAuthor(provider as Provider, pkg.author));
|
||||
|
||||
|
|
@ -163,18 +165,32 @@ function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) {
|
|||
|
||||
<div className="flex flex-col rounded-lg bg-component-bg p-3">
|
||||
<h4 className="text-fg-primary font-bold">Links</h4>
|
||||
<div className='flex flex-col'>
|
||||
<CustomA href={getPackageUrl(pkg)} includeIcon className='text-link hover:text-link-hover'>
|
||||
<div className="flex flex-col">
|
||||
<CustomA className="text-link hover:text-link-hover" href={getPackageUrl(pkg)} includeIcon>
|
||||
{provider}
|
||||
{' '}
|
||||
Page
|
||||
</CustomA>
|
||||
{(Object.entries(pkg.links) as [keyof typeof pkg.links, typeof pkg.links[keyof typeof pkg.links]][]).filter(a => a[1]).map(link =>
|
||||
typeof link[1] == "string"
|
||||
? <CustomA href={link[1]} children={upperFirst(link[0])} className='text-link hover:text-link-hover' includeIcon />
|
||||
: (link[1] as PackageDonationUrl[]).map(donationLink => <CustomA href={donationLink.url} className='text-link hover:text-link-hover' children={upperFirst(donationLink.id)} includeIcon />)
|
||||
|
||||
)}
|
||||
{(Object.entries(pkg.links) as Array<[keyof typeof pkg.links, typeof pkg.links[keyof typeof pkg.links]]>).filter(a => a[1]).map(link =>
|
||||
typeof link[1] == 'string'
|
||||
? (
|
||||
<CustomA
|
||||
children={upperFirst(link[0])}
|
||||
className="text-link hover:text-link-hover"
|
||||
href={link[1]}
|
||||
includeIcon
|
||||
key={link[0]}
|
||||
/>
|
||||
)
|
||||
: (link[1] as Array<PackageDonationUrl>).map(donationLink => (
|
||||
<CustomA
|
||||
children={upperFirst(donationLink.id)}
|
||||
className="text-link hover:text-link-hover"
|
||||
href={donationLink.url}
|
||||
includeIcon
|
||||
key={donationLink.id}
|
||||
/>
|
||||
)))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -232,26 +248,33 @@ function BrowserSidebar({ package: pkg }: { package: ManagedPackage }) {
|
|||
function InstallButton() {
|
||||
const triggerRef = useRef<HTMLDivElement>(null);
|
||||
const { provider, slug } = Route.useParams();
|
||||
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
|
||||
if (!includes(PROVIDERS, provider))
|
||||
throw new Error('invalid provider');
|
||||
const [open, setOpen] = useState(false);
|
||||
const clusters = useClusters();
|
||||
const browserContext = useBrowserContext();
|
||||
const { data: versions } = usePackageVersions(provider, slug, {
|
||||
const { data: versions, isLoading: versionsLoading } = usePackageVersions(provider, slug, {
|
||||
mc_versions: browserContext.cluster ? [browserContext.cluster.mc_version] : [],
|
||||
loaders: browserContext.cluster ? [browserContext.cluster.mc_loader] : [],
|
||||
limit: 1
|
||||
limit: 1,
|
||||
});
|
||||
const version = useMemo(() => {
|
||||
if (!versions || !browserContext.cluster)
|
||||
return undefined;
|
||||
return versions.items[0]
|
||||
return versions.items[0];
|
||||
}, [browserContext.cluster, versions]);
|
||||
|
||||
function download() {
|
||||
if (!version || !browserContext.cluster || !includes(PROVIDERS, provider))
|
||||
return false;
|
||||
downloadPackage(browserContext.cluster, provider, version);
|
||||
}
|
||||
const download = useDownloadPackage(browserContext.cluster!, provider, version!, true);
|
||||
const downloadRef = useRef(download);
|
||||
useEffect(() => {
|
||||
downloadRef.current.reset();
|
||||
}, [browserContext.cluster]);
|
||||
|
||||
// function download() {
|
||||
// if (!version || !browserContext.cluster || !includes(PROVIDERS, provider))
|
||||
// return false;
|
||||
// downloadPackage(browserContext.cluster, provider, version);
|
||||
// }
|
||||
|
||||
return (
|
||||
|
||||
|
|
@ -259,24 +282,39 @@ function InstallButton() {
|
|||
<Button
|
||||
className="max-w-full flex-1 rounded-r-none disabled:text-white/50 disabled:bg-blue-900"
|
||||
color="primary"
|
||||
isDisabled={!version}
|
||||
onClick={download}
|
||||
isDisabled={!version || download.isPending || download.isSuccess}
|
||||
onClick={() => download.mutate()}
|
||||
>
|
||||
<Download01Icon />
|
||||
<div className="w-full text-sm">
|
||||
{
|
||||
browserContext.cluster
|
||||
<Show when={download.isIdle}>
|
||||
{browserContext.cluster
|
||||
? version
|
||||
? (
|
||||
<span>
|
||||
Download latest to
|
||||
<br />
|
||||
<span className="text-md font-semibold">{browserContext.cluster.name}</span>
|
||||
</span>
|
||||
)
|
||||
: `No matching version found`
|
||||
: 'Select a Cluster'
|
||||
}
|
||||
<span>
|
||||
Download latest to
|
||||
<br />
|
||||
<span className="text-md font-semibold">{browserContext.cluster.name}</span>
|
||||
</span>
|
||||
)
|
||||
: versionsLoading ? 'Looking for a version...' : 'No matching version found'
|
||||
: 'Select a Cluster'}
|
||||
</Show>
|
||||
<Show when={download.isPending}>
|
||||
<span>
|
||||
Downloading latest to
|
||||
<br />
|
||||
<span className="text-md font-semibold">{browserContext.cluster?.name ?? 'Unknown'}</span>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={download.isSuccess}>
|
||||
<span>
|
||||
Installed latest into
|
||||
<br />
|
||||
<span className="text-md font-semibold">{browserContext.cluster?.name ?? 'Unknown'}</span>
|
||||
</span>
|
||||
</Show>
|
||||
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
|
|
@ -289,7 +327,7 @@ function InstallButton() {
|
|||
aria-label="cluster" isOpen={open}
|
||||
onOpenChange={setOpen}
|
||||
onSelectionChange={(e) => {
|
||||
const cluster = clusters?.find(cluster => cluster.id as unknown as number === e);
|
||||
const cluster = clusters?.find(item => item.id as unknown as number === e);
|
||||
if (cluster)
|
||||
browserContext.setCluster(cluster);
|
||||
}}
|
||||
|
|
@ -359,52 +397,57 @@ function colorForType(type: string) {
|
|||
}
|
||||
}
|
||||
|
||||
function Versions(){
|
||||
const {provider, slug} = Route.useParams()
|
||||
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
|
||||
const [offset, setOffset] = useState(0)
|
||||
function Versions() {
|
||||
const { provider, slug } = Route.useParams();
|
||||
if (!includes(PROVIDERS, provider))
|
||||
throw new Error('invalid provider');
|
||||
const [offset, setOffset] = useState(0);
|
||||
const { data: versions } = usePackageVersions(provider, slug, {
|
||||
limit: 20,
|
||||
offset
|
||||
offset,
|
||||
});
|
||||
|
||||
const pagination = usePagination({
|
||||
itemsCount: versions?.total as unknown as number,
|
||||
itemsPerPage: versions?.limit as unknown as number
|
||||
})
|
||||
useEffect(()=>{
|
||||
setOffset(pagination.offset)
|
||||
},[pagination.offset])
|
||||
useEffect(()=>{
|
||||
pagination.reset()
|
||||
},[versions?.total])
|
||||
return <div className='flex flex-col'>
|
||||
<div className='flex justify-between'>
|
||||
<pagination.Navigation/>
|
||||
itemsPerPage: versions?.limit as unknown as number,
|
||||
});
|
||||
const paginationRef = useRef(pagination);
|
||||
useEffect(() => {
|
||||
setOffset(pagination.offset);
|
||||
}, [pagination.offset]);
|
||||
useEffect(() => {
|
||||
paginationRef.current.reset();
|
||||
}, [versions?.total]);
|
||||
return (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex justify-between">
|
||||
<pagination.Navigation />
|
||||
</div>
|
||||
<Table className="border-separate border-spacing-x-none border-spacing-y-1 w-full">
|
||||
<TableHeader className="text-left">
|
||||
<Column className="pr-2" />
|
||||
<Column className="pr-4" isRowHeader>Name</Column>
|
||||
<Column className="pr-4">Game Versions</Column>
|
||||
<Column className="pr-4">Loaders</Column>
|
||||
<Column className="pr-4">Created</Column>
|
||||
<Column className="pr-4">Downloads</Column>
|
||||
<Column className="pr-4" />
|
||||
</TableHeader>
|
||||
<TableBody className="">
|
||||
{versions?.items.map(item =>
|
||||
<VersionRow key={item.version_id} version={item} />)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<Table className="border-separate border-spacing-x-none border-spacing-y-1">
|
||||
<TableHeader className="text-left">
|
||||
<Column className="pr-2"/>
|
||||
<Column className="pr-4" isRowHeader>Name</Column>
|
||||
<Column className="pr-4">Game Versions</Column>
|
||||
<Column className="pr-4">Loaders</Column>
|
||||
<Column className="pr-4">Created</Column>
|
||||
<Column className="pr-4">Downloads</Column>
|
||||
<Column className="pr-4"/>
|
||||
</TableHeader>
|
||||
<TableBody className="">
|
||||
{versions?.items.map(item=>
|
||||
<VersionRow version={item}/>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function VersionRow({version}:{version:ManagedVersion}){
|
||||
const {provider} = Route.useParams()
|
||||
if(!includes(PROVIDERS, provider)) throw new Error("invalid provider")
|
||||
function VersionRow({ version }: { version: ManagedVersion }) {
|
||||
const { provider } = Route.useParams();
|
||||
if (!includes(PROVIDERS, provider))
|
||||
throw new Error('invalid provider');
|
||||
return (
|
||||
<Row className="my-2 bg-page-elevated px-4 [&>td]:py-4">
|
||||
<Row className="my-2 bg-page-elevated px-4 [&>td]:py-4 w-full">
|
||||
<Cell className="p-4 my-2 bg-component-bg rounded-l-xl">
|
||||
<Tooltip text={upperFirst(version.release_type)}>
|
||||
<div className={`${colorForType(version.release_type)} h-8 w-8 flex items-center justify-center rounded-md`}>
|
||||
|
|
@ -413,22 +456,22 @@ function VersionRow({version}:{version:ManagedVersion}){
|
|||
</Tooltip>
|
||||
</Cell>
|
||||
<Cell className="p-4 px-2 bg-component-bg">{version.display_name}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{version.mc_versions.join(", ")}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{version.loaders.map(upperFirst).join(", ")}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{version.mc_versions.join(', ')}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{version.loaders.map(upperFirst).join(', ')}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{formatAsRelative(Date.parse(version.published))}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg">{version.downloads}</Cell>
|
||||
<Cell className="p-4 pl-0 bg-component-bg rounded-r-xl">
|
||||
<Modal.Trigger>
|
||||
<Button
|
||||
size='icon'
|
||||
color='secondary'
|
||||
<Button
|
||||
children={<Download01Icon />}
|
||||
color="secondary"
|
||||
size="icon"
|
||||
/>
|
||||
<ChooseClusterModal confirmText='Download' onSelected={cluster=>downloadPackage(cluster, provider, version, true)}/>
|
||||
<ChooseClusterModal confirmText="Download" onSelected={cluster => downloadPackage(cluster, provider, version, true)} />
|
||||
</Modal.Trigger>
|
||||
</Cell>
|
||||
</Row>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function downloadPackage(cluster: ClusterModel, provider: Provider, version: ManagedVersion, skipCompatibility = false) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import type { Filters } from '@/bindings.gen';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import ProviderIcon from '@/components/content/ProviderIcon';
|
||||
import { BrowserProvider, useBrowserContext } from '@/hooks/useBrowser';
|
||||
import { useClusters } from '@/hooks/useCluster';
|
||||
import { PROVIDERS } from '@/utils';
|
||||
import { browserCategories } from '@/utils/browser';
|
||||
import { AnimatedOutlet, Dropdown, Show, TextField } from '@onelauncher/common/components';
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { SearchMdIcon } from '@untitled-theme/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
|
|
@ -27,10 +28,10 @@ function RouteComponent() {
|
|||
);
|
||||
}
|
||||
|
||||
export function BrowserLayout(props: any) {
|
||||
export function BrowserLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="relative h-full flex flex-1 flex-col items-center gap-2">
|
||||
<div className="h-full w-full max-w-screen-2xl flex flex-1 flex-col items-center gap-y-2">
|
||||
<div className="h-full w-full flex flex-1 flex-col items-center gap-y-2">
|
||||
<div className="grid grid-cols-[220px_auto_220px] w-full gap-x-6">
|
||||
<div />
|
||||
<BrowserToolbar />
|
||||
|
|
@ -42,7 +43,7 @@ export function BrowserLayout(props: any) {
|
|||
|
||||
<div className="h-full flex flex-col gap-y-4">
|
||||
<div className="h-full flex-1">
|
||||
{props.children}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -56,25 +57,30 @@ export function BrowserLayout(props: any) {
|
|||
function BrowserSidebar() {
|
||||
const context = useBrowserContext();
|
||||
const clusters = useClusters();
|
||||
|
||||
useEffect(() => {
|
||||
const game_versions = context.cluster?.mc_version ? [context.cluster.mc_version] : null;
|
||||
const loaders = context.cluster?.mc_loader ? [context.cluster.mc_loader] : null;
|
||||
context.setQuery({ ...context.query, filters: { ...defaultFilters, ...context.query.filters, game_versions, loaders } });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- loop
|
||||
}, [context.cluster]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h6 className="my-1 uppercase opacity-60">Active Cluster</h6>
|
||||
<Dropdown
|
||||
onSelectionChange={(id) => {
|
||||
const cluster = clusters?.find(cluster => cluster.id.toString() === id);
|
||||
onSelectionChange={(index) => {
|
||||
if (!clusters)
|
||||
return;
|
||||
const cluster = clusters[index as number];
|
||||
context.setCluster(cluster);
|
||||
}}
|
||||
selectedKey={context.cluster?.id.toString()}
|
||||
selectedKey={context.cluster ? clusters?.indexOf(context.cluster) : undefined}
|
||||
>
|
||||
{clusters?.map(cluster => (
|
||||
<Dropdown.Item id={cluster.id.toString()} key={cluster.id}>
|
||||
{clusters?.map((cluster, index) => (
|
||||
<Dropdown.Item id={index} key={cluster.id}>
|
||||
{cluster.name}
|
||||
</Dropdown.Item>
|
||||
))}
|
||||
|
|
@ -107,7 +113,7 @@ const defaultFilters: Filters = {
|
|||
|
||||
function BrowserCategories() {
|
||||
const context = useBrowserContext();
|
||||
const categories = browserCategories.byPackageType((context.query.filters?.package_types ?? ["mod"])[0], context.provider);
|
||||
const categories = browserCategories.byPackageType((context.query.filters?.package_types ?? ['mod'])[0], context.provider);
|
||||
|
||||
function switchCategory(category: string) {
|
||||
const newCategories = context.query.filters?.categories?.includes(category)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import type { Paginated, SearchResult } from '@/bindings.gen';
|
||||
import type { PaginationOptions } from '@/hooks/usePagination';
|
||||
import { PackageGrid } from '@/components/content/PackageItem';
|
||||
import { useBrowserContext, useBrowserSearch } from '@/hooks/useBrowser';
|
||||
import usePagination from '@/hooks/usePagination';
|
||||
import { Show } from '@onelauncher/common/components';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { BrowserLayout } from './route';
|
||||
|
||||
export const Route = createFileRoute('/app/browser/search')({
|
||||
|
|
@ -25,10 +23,12 @@ function RouteComponent() {
|
|||
function Search() {
|
||||
const context = useBrowserContext();
|
||||
const search = useBrowserSearch(context.provider, context.query, {
|
||||
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
search.refetch();
|
||||
}, [context.provider, context.query, context.cluster]);
|
||||
}, [context.provider, context.query, context.cluster, search]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -44,19 +44,25 @@ function Search() {
|
|||
function Results({ results }: { results: Paginated<SearchResult> }) {
|
||||
const context = useBrowserContext();
|
||||
const [oldTotal, setOldTotal] = useState(results.total);
|
||||
|
||||
const pagination = usePagination({
|
||||
itemsCount: results.total as unknown as number,
|
||||
itemsPerPage: context.query.limit as unknown as number,
|
||||
});
|
||||
|
||||
const contextRef = useRef(context);
|
||||
|
||||
useEffect(() => {
|
||||
context.setQuery({ ...context.query, offset: pagination.offset as unknown as bigint });
|
||||
}, [pagination.page]);
|
||||
contextRef.current.setQuery(query => ({ ...query, offset: pagination.offset as unknown as bigint }));
|
||||
}, [pagination.offset, pagination.page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (oldTotal === results.total)
|
||||
return;
|
||||
pagination.reset();
|
||||
setOldTotal(results.total);
|
||||
}, [results.total]);
|
||||
}, [oldTotal, pagination, results.total]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="w-full flex justify-end my-2">
|
||||
|
|
|
|||
|
|
@ -48,9 +48,7 @@ function RouteComponent() {
|
|||
})();
|
||||
}, [cluster.data?.folder_name]);
|
||||
|
||||
const openClusterDir = async () => {
|
||||
openPath(launcherDir);
|
||||
};
|
||||
const openClusterDir = () => openPath(launcherDir);
|
||||
|
||||
async function handleEditMode() {
|
||||
if (edit)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { NewClusterCreate } from '@/components/launcher/cluster/ClusterCreation'
|
|||
import { useRecentCluster } from '@/hooks/useCluster';
|
||||
import { bindings } from '@/main';
|
||||
import { formatAsDuration, upperFirst } from '@/utils';
|
||||
import { useCommand, useCommandMut, useCommandSuspense } from '@onelauncher/common';
|
||||
import { useCommand, useCommandMut } from '@onelauncher/common';
|
||||
import { Button, Show } from '@onelauncher/common/components';
|
||||
import { createFileRoute, Link } from '@tanstack/react-router';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ function RouteComponent() {
|
|||
})();
|
||||
}, []);
|
||||
|
||||
const openLauncherDir = async () => {
|
||||
openPath(launcherDir);
|
||||
};
|
||||
const openLauncherDir = () => openPath(launcherDir);
|
||||
|
||||
const [discordRpc, setDiscordRpc] = createSetting('discord_enabled', settings?.discord_enabled);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,27 +86,28 @@ function Sidebar(props: SidebarProps) {
|
|||
|
||||
return (
|
||||
<div className="w-56 flex flex-col pr-2">
|
||||
{Object.keys(links).map((section, i) => (
|
||||
<div className="flex flex-col gap-y-2" key={i}>
|
||||
{Object.keys(links).map(section => (
|
||||
<div className="flex flex-col gap-y-2" key={section}>
|
||||
<div>
|
||||
<p className="m-1.5 mt-5 text-xs text-fg-secondary font-medium">{section.toUpperCase()}</p>
|
||||
<div className="flex flex-col gap-y-1 fill-fg-primary text-fg-primary font-medium">
|
||||
{links[section].map((link, i) => {
|
||||
// eslint yaps too much tbh
|
||||
if (!link)
|
||||
{links[section].map((entry) => {
|
||||
if (!entry)
|
||||
return '';
|
||||
|
||||
const [Component, label, link, params] = entry;
|
||||
|
||||
return (
|
||||
<a
|
||||
className={
|
||||
`px-3 py-1 rounded-md text-md border hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link[2], link[3]) ? 'bg-component-bg border-component-bg-pressed' : 'border-transparent'}`
|
||||
`px-3 py-1 rounded-md text-md border hover:bg-component-bg-hover active:bg-component-bg-pressed ${isActive(link, params) ? 'bg-component-bg border-component-bg-pressed' : 'border-transparent'}`
|
||||
}
|
||||
key={i}
|
||||
onClick={() => goto(link[2], link[3])}
|
||||
key={link}
|
||||
onClick={() => goto(link, params)}
|
||||
>
|
||||
<span className="flex flex-row items-center gap-x-3 *:w-5">
|
||||
{link[0]}
|
||||
{link[1]}
|
||||
{Component}
|
||||
{label}
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ function RouteComponent() {
|
|||
setIsForwardButtonEnabled(true);
|
||||
}, [currentStepIndex, tasksStage, tasksCompleted]);
|
||||
|
||||
const next = useCallback(async () => {
|
||||
const next = useCallback(() => {
|
||||
const isLast = currentStepIndex === steps.length - 1;
|
||||
|
||||
if (isLast) {
|
||||
|
|
@ -140,14 +140,17 @@ function RouteComponent() {
|
|||
setTasksMessage('Starting setup...');
|
||||
|
||||
const tasksToRun = [
|
||||
async () => {
|
||||
() => new Promise<void>((resolve) => {
|
||||
setTasksMessage(`Setting language to ${language?.lang}...`);
|
||||
|
||||
console.log('Language task completed for:', language?.lang);
|
||||
},
|
||||
async () => {
|
||||
|
||||
resolve();
|
||||
}),
|
||||
() => new Promise<void>((resolve) => {
|
||||
if (importInstancesMap.size === 0) {
|
||||
console.log('No instances selected for import.');
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +168,9 @@ function RouteComponent() {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
resolve();
|
||||
}),
|
||||
];
|
||||
|
||||
const results = await Promise.allSettled(tasksToRun.map(task => task()));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { PackageType, Provider } from "@/bindings.gen";
|
||||
import type { PackageType, Provider } from '@/bindings.gen';
|
||||
|
||||
export const BROWSER_VIEWS: Array<string> = ['grid', 'list'] as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ export function int32ToBigInt([high, low]: [number, number]): bigint {
|
|||
}
|
||||
|
||||
export function randomString(len: number = 6, charSet: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') {
|
||||
let randomString = '';
|
||||
let str = '';
|
||||
for (let i = 0; i < len; i++) {
|
||||
const randomPos = Math.floor(Math.random() * charSet.length);
|
||||
randomString += charSet.substring(randomPos, randomPos + 1);
|
||||
str += charSet.substring(randomPos, randomPos + 1);
|
||||
}
|
||||
return randomString;
|
||||
return str;
|
||||
}
|
||||
|
||||
export function abbreviateNumber(n: number | bigint, locale: string = 'en-US'): string {
|
||||
|
|
|
|||
|
|
@ -27,5 +27,5 @@
|
|||
"skipLibCheck": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.js"]
|
||||
"include": ["**/*.ts", "**/*.tsx", "vite.config.ts"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,22 @@ export default defineConfig(
|
|||
|
||||
return config;
|
||||
}),
|
||||
{
|
||||
ignores: [
|
||||
'apps/onelauncher/old_frontend',
|
||||
'apps/*/desktop/capabilities',
|
||||
'apps/*/desktop/gen',
|
||||
'**/*.gen.*',
|
||||
],
|
||||
// overrides: [
|
||||
// {
|
||||
// files: ['vite.config.js'],
|
||||
// parserOptions: {
|
||||
// project: null, // disables type-aware linting for this file
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'sort-imports': 'off', // Replaced by perfectionist/sort-named-imports',
|
||||
|
|
@ -31,9 +47,16 @@ export default defineConfig(
|
|||
'ts/no-use-before-define': 'off',
|
||||
'style/function-paren-newline': ['error', 'consistent'],
|
||||
'react/no-context-provider': 'off',
|
||||
"prefer-const": "off", // disabled due to maximum call stack size,
|
||||
"style/jsx-one-expression-per-line": ["error", { allow: "non-jsx" }],
|
||||
"unused-imports/no-unused-imports": "off"
|
||||
'prefer-const': 'off', // disabled due to maximum call stack size,
|
||||
'style/jsx-one-expression-per-line': ['error', { allow: 'non-jsx' }],
|
||||
'unused-imports/no-unused-imports': 'off',
|
||||
'eslint-comments/no-unlimited-disable': 'off',
|
||||
'eslint-comments/require-description': ['error', {
|
||||
ignore: [
|
||||
'eslint-disable',
|
||||
],
|
||||
}],
|
||||
'no-shadow': 'off',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
14
lint-staged.config.mjs
Normal file
14
lint-staged.config.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('lint-staged').Configuration}
|
||||
*/
|
||||
const config = {
|
||||
'*.ts': 'eslint',
|
||||
'*.{js,mjs,cjs}': 'eslint', // handle JS, MJS, CJS in one fell swoop
|
||||
'*.rs': [
|
||||
'rustfmt', // use rustfmt to only check the files provided by lint-staged
|
||||
// 'cargo clippy -- -D warnings', // sadly clippy can't do this
|
||||
],
|
||||
};
|
||||
export default config;
|
||||
11
package.json
11
package.json
|
|
@ -23,8 +23,7 @@
|
|||
"scripts": {
|
||||
"prep": "pnpm scripts prep",
|
||||
"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",
|
||||
"core": "pnpm --filter @onelauncher/core -- ",
|
||||
"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",
|
||||
|
|
@ -34,13 +33,13 @@
|
|||
"oneclient:desktop": "pnpm --filter @oneclient/desktop -- ",
|
||||
"web_common": "pnpm --filter @onelauncher/common -- ",
|
||||
"scripts": "pnpm --filter @onelauncher/scripts -- ",
|
||||
|
||||
"format": "pnpm format:es && pnpm format:rs",
|
||||
"format:es": "pnpm lint:es --fix",
|
||||
"format:rs": "cargo +nightly fmt && cargo clippy --fix",
|
||||
"lint": "pnpm lint:es && pnpm lint:rs",
|
||||
"lint:es": "eslint . --cache --flag unstable_ts_config",
|
||||
"lint:rs": "cargo +nightly fmt --check && cargo clippy -- -D warnings"
|
||||
"lint:es": "eslint . --cache",
|
||||
"lint:rs": "cargo +nightly fmt --check && cargo clippy -- -D warnings",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
|
@ -54,6 +53,8 @@
|
|||
"@tanstack/eslint-config": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.2",
|
||||
"rimraf": "catalog:",
|
||||
"tsx": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
use std::path::Path;
|
||||
|
||||
use onelauncher_entity::{cluster_groups, clusters, icon::Icon, loader::GameLoader};
|
||||
use sea_orm::{ActiveValue::Set, IntoActiveModel, prelude::*};
|
||||
use onelauncher_entity::icon::Icon;
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
use onelauncher_entity::{cluster_groups, clusters};
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use sea_orm::IntoActiveModel;
|
||||
use sea_orm::prelude::*;
|
||||
|
||||
use crate::{error::{DaoError, LauncherResult}, store::State, utils::io};
|
||||
use crate::error::{DaoError, LauncherResult};
|
||||
use crate::store::State;
|
||||
use crate::utils::io;
|
||||
|
||||
pub type ClusterId = i64;
|
||||
|
||||
|
|
@ -35,11 +41,10 @@ pub async fn insert_cluster(
|
|||
}
|
||||
|
||||
/// Updates an existing cluster in the database.
|
||||
pub async fn update_cluster_by_id<B>(
|
||||
id: ClusterId,
|
||||
block: B,
|
||||
) -> LauncherResult<clusters::Model>
|
||||
where B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel> {
|
||||
pub async fn update_cluster_by_id<B>(id: ClusterId, block: B) -> LauncherResult<clusters::Model>
|
||||
where
|
||||
B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel>,
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
|
|
@ -54,7 +59,9 @@ pub async fn update_cluster<B>(
|
|||
cluster: &mut clusters::Model,
|
||||
block: B,
|
||||
) -> LauncherResult<&mut clusters::Model>
|
||||
where B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel> {
|
||||
where
|
||||
B: AsyncFnOnce(clusters::ActiveModel) -> LauncherResult<clusters::ActiveModel>,
|
||||
{
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
|
|
@ -72,9 +79,7 @@ pub async fn delete_cluster_by_id(id: ClusterId) -> LauncherResult<()> {
|
|||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let deleted = clusters::Entity::delete_by_id(id)
|
||||
.exec(db)
|
||||
.await?;
|
||||
let deleted = clusters::Entity::delete_by_id(id).exec(db).await?;
|
||||
|
||||
if deleted.rows_affected == 0 {
|
||||
return Err(DaoError::NotFound.into());
|
||||
|
|
@ -88,15 +93,15 @@ pub async fn get_cluster_by_id(id: ClusterId) -> LauncherResult<Option<clusters:
|
|||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let model = clusters::Entity::find_by_id(id)
|
||||
.one(db)
|
||||
.await?;
|
||||
let model = clusters::Entity::find_by_id(id).one(db).await?;
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
/// Gets a cluster by its path from the database.
|
||||
pub async fn get_cluster_by_folder_name(folder_name: &Path) -> LauncherResult<Option<clusters::Model>> {
|
||||
pub async fn get_cluster_by_folder_name(
|
||||
folder_name: &Path,
|
||||
) -> LauncherResult<Option<clusters::Model>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
|
|
@ -126,29 +131,26 @@ pub async fn get_all_clusters() -> LauncherResult<Vec<clusters::Model>> {
|
|||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let clusters = clusters::Entity::find()
|
||||
.all(db)
|
||||
.await?;
|
||||
let clusters = clusters::Entity::find().all(db).await?;
|
||||
|
||||
Ok(clusters)
|
||||
}
|
||||
|
||||
/// Gets all clusters grouped by their group from the database.
|
||||
pub async fn get_clusters_grouped() -> LauncherResult<Vec<(cluster_groups::Model, Vec<clusters::Model>)>> {
|
||||
pub async fn get_clusters_grouped()
|
||||
-> LauncherResult<Vec<(cluster_groups::Model, Vec<clusters::Model>)>> {
|
||||
let state = State::get().await?;
|
||||
let db = &state.db;
|
||||
|
||||
let groups = cluster_groups::Entity::find()
|
||||
.all(db)
|
||||
.await?;
|
||||
let groups = cluster_groups::Entity::find().all(db).await?;
|
||||
|
||||
let clusters = groups.load_many(clusters::Entity, db)
|
||||
.await?;
|
||||
let clusters = groups.load_many(clusters::Entity, db).await?;
|
||||
|
||||
let mut grouped = Vec::new();
|
||||
|
||||
for (index, group) in groups.iter().enumerate() {
|
||||
let group_clusters: Vec<clusters::Model> = clusters.get(index).cloned().unwrap_or_else(Vec::new);
|
||||
let group_clusters: Vec<clusters::Model> =
|
||||
clusters.get(index).cloned().unwrap_or_else(Vec::new);
|
||||
|
||||
grouped.push((group.clone(), group_clusters));
|
||||
}
|
||||
|
|
@ -157,7 +159,8 @@ pub async fn get_clusters_grouped() -> LauncherResult<Vec<(cluster_groups::Model
|
|||
}
|
||||
|
||||
/// Caches an icon from a file path and updates the cluster to use the cached icon.
|
||||
/// This function copies the image to the icon cache directory and sets the cluster's icon_url
|
||||
///
|
||||
/// This function copies the image to the icon cache directory and sets the cluster's `icon_url`
|
||||
/// to reference the cached version.
|
||||
pub async fn set_cluster_icon_by_path(
|
||||
cluster: &mut clusters::Model,
|
||||
|
|
@ -171,27 +174,38 @@ pub async fn set_cluster_icon_by_path(
|
|||
|
||||
let image_bytes = io::read(icon_path).await?;
|
||||
|
||||
let hash = crate::utils::crypto::HashAlgorithm::Sha1.hash(&image_bytes).await?;
|
||||
let hash = crate::utils::crypto::HashAlgorithm::Sha1
|
||||
.hash(&image_bytes)
|
||||
.await?;
|
||||
|
||||
let cache_dir = crate::store::Dirs::get_caches_dir().await?.join("icons");
|
||||
io::create_dir_all(&cache_dir).await?;
|
||||
|
||||
let cached_file_path = cache_dir.join(format!("{}.png", hash));
|
||||
let cached_file_path = cache_dir.join(format!("{hash}.png"));
|
||||
io::write(&cached_file_path, &image_bytes).await?;
|
||||
|
||||
let file_icon = Icon::try_from_path(&cached_file_path)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to create file icon from path: {}", cached_file_path.display()))?;
|
||||
let file_icon = Icon::try_from_path(&cached_file_path).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to create file icon from path: {}",
|
||||
cached_file_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
update_cluster(cluster, |mut active_model: clusters::ActiveModel| async move {
|
||||
active_model.icon_url = Set(Some(file_icon));
|
||||
Ok(active_model)
|
||||
}).await?;
|
||||
update_cluster(
|
||||
cluster,
|
||||
|mut active_model: clusters::ActiveModel| async move {
|
||||
active_model.icon_url = Set(Some(file_icon));
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Caches an icon from a file path and updates the cluster by ID to use the cached icon.
|
||||
/// This function copies the image to the icon cache directory and sets the cluster's icon_url
|
||||
///
|
||||
/// This function copies the image to the icon cache directory and sets the cluster's `icon_url`
|
||||
/// to reference the cached version.
|
||||
pub async fn set_icon_by_id(
|
||||
cluster_id: ClusterId,
|
||||
|
|
@ -205,21 +219,31 @@ pub async fn set_icon_by_id(
|
|||
|
||||
let image_bytes = io::read(icon_path).await?;
|
||||
|
||||
let hash = crate::utils::crypto::HashAlgorithm::Sha1.hash(&image_bytes).await?;
|
||||
let hash = crate::utils::crypto::HashAlgorithm::Sha1
|
||||
.hash(&image_bytes)
|
||||
.await?;
|
||||
|
||||
let cache_dir = crate::store::Dirs::get_caches_dir().await?.join("icons");
|
||||
io::create_dir_all(&cache_dir).await?;
|
||||
|
||||
let cached_file_path = cache_dir.join(format!("{}.png", hash));
|
||||
let cached_file_path = cache_dir.join(format!("{hash}.png"));
|
||||
io::write(&cached_file_path, &image_bytes).await?;
|
||||
|
||||
let file_icon = Icon::try_from_path(&cached_file_path)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to create file icon from path: {}", cached_file_path.display()))?;
|
||||
let file_icon = Icon::try_from_path(&cached_file_path).ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"Failed to create file icon from path: {}",
|
||||
cached_file_path.display()
|
||||
)
|
||||
})?;
|
||||
|
||||
let updated_cluster = update_cluster_by_id(cluster_id, |mut active_model: clusters::ActiveModel| async move {
|
||||
active_model.icon_url = Set(Some(file_icon));
|
||||
Ok(active_model)
|
||||
}).await?;
|
||||
let updated_cluster = update_cluster_by_id(
|
||||
cluster_id,
|
||||
|mut active_model: clusters::ActiveModel| async move {
|
||||
active_model.icon_url = Set(Some(file_icon));
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(updated_cluster)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
//! Authentication flow manager
|
||||
|
||||
use crate::{error::LauncherResult, store::{credentials::{MinecraftCredentials, MinecraftLogin}, State}};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::State;
|
||||
use crate::store::credentials::{MinecraftCredentials, MinecraftLogin};
|
||||
|
||||
/// Begin a Microsoft authentication flow.
|
||||
#[tracing::instrument]
|
||||
|
|
@ -77,15 +79,13 @@ pub async fn get_user(user: uuid::Uuid) -> LauncherResult<Option<MinecraftCreden
|
|||
let state = State::get().await?;
|
||||
let store = state.credentials.read().await;
|
||||
|
||||
let user = store
|
||||
.users
|
||||
.get(&user)
|
||||
.cloned();
|
||||
let user = store.users.get(&user).cloned();
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
/// Get a fake user for testing or offline mode.
|
||||
#[must_use]
|
||||
pub fn get_fake_user() -> MinecraftCredentials {
|
||||
MinecraftCredentials {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
|
|
@ -94,4 +94,4 @@ pub fn get_fake_user() -> MinecraftCredentials {
|
|||
refresh_token: "0".to_string(),
|
||||
expires: chrono::Utc::now() + chrono::Duration::days(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
|
||||
use futures::{pin_mut, AsyncReadExt, TryStreamExt};
|
||||
use futures::{AsyncReadExt, TryStreamExt, pin_mut};
|
||||
use interpulse::api::minecraft::{Argument, ArgumentValue, Library, VersionType};
|
||||
use interpulse::api::modded::SidedDataEntry;
|
||||
use interpulse::utils::get_path_from_artifact;
|
||||
|
|
@ -11,8 +11,8 @@ use onelauncher_entity::resolution::Resolution;
|
|||
|
||||
use crate::constants::{self, DUMMY_REPLACE_NEWLINE};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::credentials::MinecraftCredentials;
|
||||
use crate::store::Core;
|
||||
use crate::store::credentials::MinecraftCredentials;
|
||||
use crate::utils::io;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
|
@ -178,23 +178,31 @@ pub async fn main_class(path: String) -> LauncherResult<Option<String>> {
|
|||
let mut entry_reader = match reader.reader_without_entry(index).await {
|
||||
Ok(reader) => reader,
|
||||
Err(err) => {
|
||||
tracing::error!("failed to get entry reader for {}: {}", entry.filename().as_str().unwrap_or_default(), err);
|
||||
tracing::error!(
|
||||
"failed to get entry reader for {}: {}",
|
||||
entry.filename().as_str().unwrap_or_default(),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = entry_reader.read_to_string(&mut buf).await {
|
||||
tracing::error!("failed to read entry {}: {}", entry.filename().as_str().unwrap_or_default(), err);
|
||||
tracing::error!(
|
||||
"failed to read entry {}: {}",
|
||||
entry.filename().as_str().unwrap_or_default(),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
for line in buf.lines() {
|
||||
let line = line.trim();
|
||||
if line.starts_with("Main-Class:") {
|
||||
if let Some(class) = line.split(':').nth(1) {
|
||||
class_name = Some(class.trim().to_string());
|
||||
break;
|
||||
}
|
||||
if line.starts_with("Main-Class:")
|
||||
&& let Some(class) = line.split(':').nth(1)
|
||||
{
|
||||
class_name = Some(class.trim().to_string());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -282,9 +290,10 @@ pub fn classpaths(
|
|||
.iter()
|
||||
.filter_map(|lib| {
|
||||
if let Some(rules) = &lib.rules
|
||||
&& !super::rules::validate_rules(rules, java_arch, updated) {
|
||||
return None;
|
||||
}
|
||||
&& !super::rules::validate_rules(rules, java_arch, updated)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if !lib.include_in_classpath {
|
||||
return None;
|
||||
|
|
@ -294,11 +303,7 @@ pub fn classpaths(
|
|||
})
|
||||
.collect::<Result<HashSet<_>, _>>()?;
|
||||
|
||||
classpaths.insert(
|
||||
io::canonicalize(client_path)?
|
||||
.to_string_lossy()
|
||||
.to_string(),
|
||||
);
|
||||
classpaths.insert(io::canonicalize(client_path)?.to_string_lossy().to_string());
|
||||
|
||||
Ok(classpaths
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use interpulse::api::modded::LoaderVersion;
|
|||
use onelauncher_entity::loader::GameLoader;
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::api::ingress::{init_ingress, IngressSendExt};
|
||||
use crate::api::ingress::{IngressSendExt, init_ingress};
|
||||
use crate::error::{LauncherError, LauncherResult};
|
||||
use crate::store::ingress::{IngressType, SubIngress, SubIngressExt};
|
||||
use crate::store::metadata::MetadataError;
|
||||
|
|
@ -27,7 +27,13 @@ pub async fn download_minecraft_ingressed(
|
|||
)
|
||||
.await?;
|
||||
|
||||
download_minecraft(version, java_arch, Some(&SubIngress::new(&id, 100.0)), force).await
|
||||
download_minecraft(
|
||||
version,
|
||||
java_arch,
|
||||
Some(&SubIngress::new(&id, 100.0)),
|
||||
force,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// MARK: Main
|
||||
|
|
@ -38,7 +44,6 @@ pub async fn download_minecraft(
|
|||
sub_ingress: Option<&SubIngress<'_>>,
|
||||
force: Option<bool>,
|
||||
) -> LauncherResult<()> {
|
||||
|
||||
let asset_index = download_assets_index(version, sub_ingress, force).await?;
|
||||
tokio::try_join! {
|
||||
download_assets(version.assets == "legacy", asset_index, sub_ingress, force),
|
||||
|
|
@ -81,8 +86,7 @@ pub async fn download_version_info(
|
|||
);
|
||||
|
||||
let mut info =
|
||||
http::fetch_json_advanced(Method::GET, &version.url, None, None, None, ingress)
|
||||
.await?;
|
||||
http::fetch_json_advanced(Method::GET, &version.url, None, None, None, ingress).await?;
|
||||
|
||||
if let Some(loader) = loader {
|
||||
let partial: interpulse::api::modded::PartialVersionInfo =
|
||||
|
|
@ -104,7 +108,14 @@ pub async fn download_version_info(
|
|||
info
|
||||
};
|
||||
|
||||
ingress.send_ingress(ingress.ingress_total().map(|i| i / TASKS).unwrap_or_default()).await?;
|
||||
ingress
|
||||
.send_ingress(
|
||||
ingress
|
||||
.ingress_total()
|
||||
.map(|i| i / TASKS)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
tracing::debug!("loaded minecraft version info for minecraft version {version_id}");
|
||||
Ok(result)
|
||||
|
|
@ -143,7 +154,9 @@ pub async fn download_assets_index(
|
|||
&version.asset_index.url,
|
||||
path,
|
||||
None,
|
||||
ingress.map(|i| SubIngress::from_sub(i, ingress_step)).as_ref(),
|
||||
ingress
|
||||
.map(|i| SubIngress::from_sub(i, ingress_step))
|
||||
.as_ref(),
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
|
|
@ -440,5 +453,5 @@ pub async fn get_loaders_for_version(mc_version: &str) -> LauncherResult<Vec<Gam
|
|||
let state = State::get().await?;
|
||||
let mut metadata = state.metadata.write().await;
|
||||
|
||||
Ok(metadata.get_loaders_for_version(mc_version).await?)
|
||||
metadata.get_loaders_for_version(mc_version).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::api::ingress::{init_ingress, init_ingress_opt, send_ingress};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::ingress::{IngressType, SubIngress};
|
||||
use crate::store::Dirs;
|
||||
use crate::store::ingress::{IngressType, SubIngress};
|
||||
use crate::utils::http::{fetch_advanced, fetch_json};
|
||||
use crate::utils::io::{self, IOError};
|
||||
|
||||
|
|
@ -94,7 +94,6 @@ pub async fn check_java_runtime(
|
|||
|
||||
io::write(&file, JAVA_INFO_CLASS).await?;
|
||||
id.map(async |id| send_ingress(id, 25.0).await);
|
||||
|
||||
let java_info = tokio::process::Command::new(absolute_path)
|
||||
.arg("-cp")
|
||||
.arg(dir.path())
|
||||
|
|
@ -179,7 +178,10 @@ pub async fn prepare_java(major: u32) -> LauncherResult<java_versions::Model> {
|
|||
}
|
||||
}
|
||||
|
||||
tracing::warn!("no java installations found on the system, attempting to download java {}", major);
|
||||
tracing::warn!(
|
||||
"no java installations found on the system, attempting to download java {}",
|
||||
major
|
||||
);
|
||||
|
||||
let java_path = install_java_from_major(major).await?;
|
||||
let java_info = check_java_runtime(&java_path, false).await?;
|
||||
|
|
@ -194,6 +196,20 @@ pub async fn prepare_java(major: u32) -> LauncherResult<java_versions::Model> {
|
|||
Err(JavaError::MissingJava.into())
|
||||
}
|
||||
|
||||
// share the code that I stole from the windows one anyway to use in UNIX settings
|
||||
|
||||
#[cfg(unix)]
|
||||
fn find_java_in_path(mut found: Vec<PathBuf>) -> Vec<PathBuf> {
|
||||
if let Ok(path) = std::env::var("PATH") {
|
||||
for path_entry in path.split(':') {
|
||||
let java = PathBuf::from(path_entry).join("java");
|
||||
if java.exists() && java.is_file() && !found.contains(&java) {
|
||||
found.push(java);
|
||||
}
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
/// Attempts to scan common paths for Java installations.
|
||||
///
|
||||
/// **Can be heavy on I/O, don't run often!**
|
||||
|
|
@ -219,7 +235,6 @@ pub async fn locate_java() -> LauncherResult<HashMap<PathBuf, JavaInfo>> {
|
|||
tracing::warn!("java installation at '{}' is not valid", path.display());
|
||||
continue;
|
||||
};
|
||||
|
||||
valid.insert(path, info);
|
||||
}
|
||||
|
||||
|
|
@ -299,6 +314,8 @@ fn internal_locate_java() -> Vec<PathBuf> {
|
|||
}
|
||||
}
|
||||
|
||||
found = find_java_in_path(found);
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +350,10 @@ fn internal_locate_java() -> Vec<PathBuf> {
|
|||
}
|
||||
}
|
||||
|
||||
// check env vars
|
||||
// woowoooo mutation
|
||||
found = find_java_in_path(found);
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
|
|
@ -353,12 +374,10 @@ pub async fn get_zulu_packages() -> LauncherResult<Vec<JavaPackage>> {
|
|||
pub async fn install_java_from_major(version: u32) -> LauncherResult<PathBuf> {
|
||||
let packages = get_zulu_packages().await?;
|
||||
|
||||
let package = packages.into_iter()
|
||||
let package = packages
|
||||
.into_iter()
|
||||
.find(|p| p.java_version.contains(&version))
|
||||
.ok_or_else(|| anyhow::anyhow!(
|
||||
"Could not find a java package for version {}",
|
||||
version
|
||||
))?;
|
||||
.ok_or_else(|| anyhow::anyhow!("Could not find a java package for version {}", version))?;
|
||||
|
||||
install_java_package(package).await
|
||||
}
|
||||
|
|
@ -388,19 +407,18 @@ pub async fn install_java_package(package: JavaPackage) -> LauncherResult<PathBu
|
|||
.await
|
||||
.map_err(IOError::AsyncZipError)?;
|
||||
|
||||
if let Some(entry) = archive.file().entries().first() {
|
||||
if let Some(dir) = entry
|
||||
if let Some(entry) = archive.file().entries().first()
|
||||
&& let Some(dir) = entry
|
||||
.filename()
|
||||
.clone()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
.split('/')
|
||||
.next()
|
||||
{
|
||||
let path = java_dir.join(dir);
|
||||
if path.exists() {
|
||||
io::remove_dir_all(&path).await?;
|
||||
}
|
||||
{
|
||||
let path = java_dir.join(dir);
|
||||
if path.exists() {
|
||||
io::remove_dir_all(&path).await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -417,7 +435,12 @@ pub async fn install_java_package(package: JavaPackage) -> LauncherResult<PathBu
|
|||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let java_version = package.java_version.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(".");
|
||||
let java_version = package
|
||||
.java_version
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(".");
|
||||
base_path = base_path
|
||||
.join(format!("zulu-{java_version}.jre"))
|
||||
.join("Contents")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use onelauncher_entity::{loader::GameLoader, package::{PackageType, Provider}};
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
use onelauncher_entity::package::{PackageType, Provider};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
|
||||
|
|
@ -23,17 +24,21 @@ pub enum Sort {
|
|||
Relevance,
|
||||
Downloads,
|
||||
Newest,
|
||||
Updated
|
||||
Updated,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Sort {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::Relevance => "relevance",
|
||||
Self::Downloads => "downloads",
|
||||
Self::Newest => "newest",
|
||||
Self::Updated => "updated",
|
||||
})
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Relevance => "relevance",
|
||||
Self::Downloads => "downloads",
|
||||
Self::Newest => "newest",
|
||||
Self::Updated => "updated",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,28 +53,28 @@ pub struct SearchQuery {
|
|||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SearchResult {
|
||||
pub project_id: String,
|
||||
pub project_type: String,
|
||||
pub slug: String,
|
||||
pub author: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
pub display_categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub downloads: usize,
|
||||
pub icon_url: String,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub latest_version: String,
|
||||
pub license: String,
|
||||
pub client_side: PackageSide,
|
||||
pub server_side: PackageSide,
|
||||
pub project_id: String,
|
||||
pub project_type: String,
|
||||
pub slug: String,
|
||||
pub author: String,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub categories: Vec<String>,
|
||||
pub display_categories: Vec<String>,
|
||||
pub versions: Vec<String>,
|
||||
pub downloads: usize,
|
||||
pub icon_url: String,
|
||||
pub date_created: DateTime<Utc>,
|
||||
pub date_modified: DateTime<Utc>,
|
||||
pub latest_version: String,
|
||||
pub license: String,
|
||||
pub client_side: PackageSide,
|
||||
pub server_side: PackageSide,
|
||||
/// List of URLs to images
|
||||
pub gallery: Vec<String>,
|
||||
// pub color: i64,
|
||||
pub gallery: Vec<String>,
|
||||
// pub color: i64,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
|
|
@ -98,7 +103,7 @@ pub struct ManagedPackage {
|
|||
pub links: PackageLinks,
|
||||
pub status: PackageStatus,
|
||||
pub downloads: usize,
|
||||
pub gallery: Vec<PackageGallery>
|
||||
pub gallery: Vec<PackageGallery>,
|
||||
}
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
|
|
@ -179,7 +184,7 @@ pub enum PackageSide {
|
|||
Unsupported,
|
||||
}
|
||||
|
||||
/// https://spdx.org/licenses/
|
||||
/// <https://spdx.org/licenses/>
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PackageLicense {
|
||||
|
|
@ -188,7 +193,7 @@ pub struct PackageLicense {
|
|||
pub url: Option<String>,
|
||||
}
|
||||
|
||||
/// https://docs.curseforge.com/rest-api/#tocS_ModLinks
|
||||
/// <https://docs.curseforge.com/rest-api/#tocS_ModLinks>
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct PackageLinks {
|
||||
|
|
@ -214,10 +219,10 @@ pub struct PackageGallery {
|
|||
pub thumbnail_url: String,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub featured: Option<bool>
|
||||
pub featured: Option<bool>,
|
||||
}
|
||||
|
||||
/// https://api.modrinth.com/v2/tag/donation_platform
|
||||
/// <https://api.modrinth.com/v2/tag/donation_platform>
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
|
|
@ -262,4 +267,4 @@ pub enum PackageReleaseType {
|
|||
Release,
|
||||
Beta,
|
||||
Alpha,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ pub async fn download_package(
|
|||
.await?;
|
||||
|
||||
let icon = if let Some(icon_url) = &package.icon_url {
|
||||
if let Some(icon) = Icon::try_from_url(url::Url::parse(icon_url)?) {
|
||||
if let Some(icon) = Icon::try_from_url(&url::Url::parse(icon_url)?) {
|
||||
Some(icon::cache_icon(&icon).await?)
|
||||
} else {
|
||||
None
|
||||
|
|
@ -142,12 +142,24 @@ pub async fn link_package(
|
|||
|
||||
tracing::trace!("checking compatibility of package with cluster");
|
||||
if !skip_compatibility.unwrap_or(false) {
|
||||
if !package.mc_loader.iter().any(|v| cluster.mc_loader.compatible_with(v)) {
|
||||
return Err(LauncherError::from(PackageError::from(IncompatiblePackageType::Loader)));
|
||||
if !package
|
||||
.mc_loader
|
||||
.iter()
|
||||
.any(|v| cluster.mc_loader.compatible_with(v))
|
||||
{
|
||||
return Err(LauncherError::from(PackageError::from(
|
||||
IncompatiblePackageType::Loader,
|
||||
)));
|
||||
}
|
||||
|
||||
if !package.mc_versions.iter().any(|v| cluster.mc_version.contains(v)) {
|
||||
return Err(LauncherError::from(PackageError::from(IncompatiblePackageType::McVersion)));
|
||||
if !package
|
||||
.mc_versions
|
||||
.iter()
|
||||
.any(|v| cluster.mc_version.contains(v))
|
||||
{
|
||||
return Err(LauncherError::from(PackageError::from(
|
||||
IncompatiblePackageType::McVersion,
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,7 +172,9 @@ pub async fn link_package(
|
|||
io::create_dir_all(&dest_dir).await?;
|
||||
|
||||
let dest_pkg = dest_dir.join(package.file_name.as_str());
|
||||
tokio::fs::hard_link(src_pkg, dest_pkg).await.map_err(IOError::from)?;
|
||||
tokio::fs::hard_link(src_pkg, dest_pkg)
|
||||
.await
|
||||
.map_err(IOError::from)?;
|
||||
|
||||
dao::link_package_to_cluster(package, cluster).await?;
|
||||
|
||||
|
|
@ -174,9 +188,17 @@ pub async fn link_package(
|
|||
}
|
||||
|
||||
fn path_from_model(dirs: &Dirs, model: &packages::Model) -> std::path::PathBuf {
|
||||
join_package_file(dirs.package_dir(&model.package_type, &model.provider, &model.package_id), &model.version_id, model.file_name.as_str())
|
||||
join_package_file(
|
||||
dirs.package_dir(&model.package_type, &model.provider, &model.package_id),
|
||||
&model.version_id,
|
||||
model.file_name.as_str(),
|
||||
)
|
||||
}
|
||||
|
||||
fn join_package_file(path: impl AsRef<std::path::Path>, version_id: &str, file_name: &str) -> std::path::PathBuf {
|
||||
fn join_package_file(
|
||||
path: impl AsRef<std::path::Path>,
|
||||
version_id: &str,
|
||||
file_name: &str,
|
||||
) -> std::path::PathBuf {
|
||||
path.as_ref().join(format!("{version_id}-{file_name}"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
use tauri::{AppHandle, Manager};
|
||||
use tracing::{error, warn};
|
||||
|
||||
use crate::{api::{proxy::{event::LauncherEvent, message::MessageLevel, LauncherProxy}, tauri::LauncherEventEmitter}, LauncherResult};
|
||||
use crate::LauncherResult;
|
||||
use crate::api::proxy::LauncherProxy;
|
||||
use crate::api::proxy::event::LauncherEvent;
|
||||
use crate::api::proxy::message::MessageLevel;
|
||||
use crate::api::tauri::LauncherEventEmitter;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProxyTauri {
|
||||
|
|
@ -13,7 +17,10 @@ impl ProxyTauri {
|
|||
#[must_use]
|
||||
pub fn new(handle: AppHandle) -> Self {
|
||||
tracing::debug!("using tauri bridge");
|
||||
tracing::debug!("webview version: {}", tauri::webview_version().unwrap_or("unknown".into()));
|
||||
tracing::debug!(
|
||||
"webview version: {}",
|
||||
tauri::webview_version().unwrap_or_else(|_| "unknown".into())
|
||||
);
|
||||
|
||||
Self {
|
||||
emitter: LauncherEventEmitter::new(handle.clone()),
|
||||
|
|
@ -27,7 +34,7 @@ impl LauncherProxy for ProxyTauri {
|
|||
async fn send_event(&self, event: LauncherEvent) -> LauncherResult<()> {
|
||||
if let LauncherEvent::Message(message) = &event {
|
||||
match message.level {
|
||||
MessageLevel::Info => {},
|
||||
MessageLevel::Info => {}
|
||||
MessageLevel::Warn => warn!("{}", message.message),
|
||||
MessageLevel::Error => error!("{}", message.message),
|
||||
}
|
||||
|
|
@ -59,5 +66,4 @@ impl LauncherProxy for ProxyTauri {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
use crate::api::packages::data::{
|
||||
ManagedPackage, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult,
|
||||
};
|
||||
use crate::api::packages::provider::ProviderExt;
|
||||
use crate::store::{Settings, State};
|
||||
use crate::utils::pagination::Paginated;
|
||||
use interpulse::api::minecraft::Version;
|
||||
use onelauncher_entity::{icon::Icon, loader::GameLoader, package::Provider, packages, resolution::Resolution};
|
||||
use onelauncher_entity::icon::Icon;
|
||||
use onelauncher_entity::loader::GameLoader;
|
||||
use onelauncher_entity::package::Provider;
|
||||
use onelauncher_entity::packages;
|
||||
use onelauncher_entity::resolution::Resolution;
|
||||
use sea_orm::ActiveValue::Set;
|
||||
use crate::{api::packages::{data::{ManagedPackage, ManagedUser, ManagedVersion, PackageAuthor, SearchQuery, SearchResult}, provider::ProviderExt}, store::{Settings, State}, utils::pagination::Paginated};
|
||||
use tauri::{AppHandle, Runtime};
|
||||
|
||||
use crate::{api::{self, cluster::dao::ClusterId}, error::{LauncherError, LauncherResult}, store::{credentials::MinecraftCredentials, Core}};
|
||||
use crate::api::cluster::dao::ClusterId;
|
||||
use crate::api::{self};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::Core;
|
||||
use crate::store::credentials::MinecraftCredentials;
|
||||
|
||||
use onelauncher_entity::prelude::model::*;
|
||||
|
||||
#[taurpc::procedures(path = "core")]
|
||||
pub trait TauriLauncherApi {
|
||||
|
||||
// Clusters
|
||||
#[taurpc(alias = "getClusters")]
|
||||
async fn get_clusters() -> LauncherResult<Vec<Cluster>>;
|
||||
|
|
@ -36,7 +48,6 @@ pub trait TauriLauncherApi {
|
|||
#[taurpc(alias = "getWorlds")]
|
||||
async fn get_worlds(id: ClusterId) -> LauncherResult<Vec<String>>;
|
||||
|
||||
|
||||
// Setting Profiles
|
||||
#[taurpc(alias = "getProfileOrDefault")]
|
||||
async fn get_profile_or_default(name: Option<String>) -> LauncherResult<SettingsProfile>;
|
||||
|
|
@ -45,7 +56,10 @@ pub trait TauriLauncherApi {
|
|||
async fn get_global_profile() -> SettingsProfile;
|
||||
|
||||
#[taurpc(alias = "updateClusterProfile")]
|
||||
async fn update_cluster_profile(name: String, profile: ProfileUpdate) -> LauncherResult<SettingsProfile>;
|
||||
async fn update_cluster_profile(
|
||||
name: String,
|
||||
profile: ProfileUpdate,
|
||||
) -> LauncherResult<SettingsProfile>;
|
||||
|
||||
// Game Metadata
|
||||
#[taurpc(alias = "getGameVersions")]
|
||||
|
|
@ -54,7 +68,6 @@ pub trait TauriLauncherApi {
|
|||
#[taurpc(alias = "getLoadersForVersion")]
|
||||
async fn get_loaders_for_version(mc_version: String) -> LauncherResult<Vec<GameLoader>>;
|
||||
|
||||
|
||||
// Users
|
||||
#[taurpc(alias = "getUsers")]
|
||||
async fn get_users() -> LauncherResult<Vec<MinecraftCredentials>>;
|
||||
|
|
@ -66,13 +79,17 @@ pub trait TauriLauncherApi {
|
|||
async fn remove_user(uuid: uuid::Uuid) -> LauncherResult<()>;
|
||||
|
||||
#[taurpc(alias = "getDefaultUser")]
|
||||
async fn get_default_user(fallback: Option<bool>) -> LauncherResult<Option<MinecraftCredentials>>;
|
||||
async fn get_default_user(
|
||||
fallback: Option<bool>,
|
||||
) -> LauncherResult<Option<MinecraftCredentials>>;
|
||||
|
||||
#[taurpc(alias = "setDefaultUser")]
|
||||
async fn set_default_user(uuid: Option<uuid::Uuid>) -> LauncherResult<()>;
|
||||
|
||||
#[taurpc(alias = "openMsaLogin")]
|
||||
async fn open_msa_login<R: Runtime>(app_handle: AppHandle<R>) -> LauncherResult<Option<MinecraftCredentials>>;
|
||||
async fn open_msa_login<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
) -> LauncherResult<Option<MinecraftCredentials>>;
|
||||
|
||||
// Settings
|
||||
#[taurpc(alias = "readSettings")]
|
||||
|
|
@ -83,28 +100,49 @@ pub trait TauriLauncherApi {
|
|||
|
||||
// Packages
|
||||
#[taurpc(alias = "searchPackages")]
|
||||
async fn search_packages(provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<SearchResult>>;
|
||||
async fn search_packages(
|
||||
provider: Provider,
|
||||
query: SearchQuery,
|
||||
) -> LauncherResult<Paginated<SearchResult>>;
|
||||
|
||||
#[taurpc(alias = "getPackage")]
|
||||
async fn get_package(provider: Provider, slug: String) -> LauncherResult<ManagedPackage>;
|
||||
|
||||
#[taurpc(alias = "getMultiplePackages")]
|
||||
async fn get_multiple_packages(provider: Provider, slugs: Vec<String>) -> LauncherResult<Vec<ManagedPackage>>;
|
||||
async fn get_multiple_packages(
|
||||
provider: Provider,
|
||||
slugs: Vec<String>,
|
||||
) -> LauncherResult<Vec<ManagedPackage>>;
|
||||
|
||||
#[taurpc(alias = "getPackageVersions")]
|
||||
async fn get_package_versions(provider: Provider, slug: String, mc_versions: Option<Vec<String>>, loaders: Option<Vec<GameLoader>>, offset: usize, limit: usize) -> LauncherResult<Paginated<ManagedVersion>>;
|
||||
async fn get_package_versions(
|
||||
provider: Provider,
|
||||
slug: String,
|
||||
mc_versions: Option<Vec<String>>,
|
||||
loaders: Option<Vec<GameLoader>>,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
) -> LauncherResult<Paginated<ManagedVersion>>;
|
||||
|
||||
#[taurpc(alias = "getPackageUser")]
|
||||
async fn get_package_user(provider: Provider, slug: String) -> LauncherResult<ManagedUser>;
|
||||
|
||||
#[taurpc(alias = "downloadPackage")]
|
||||
async fn download_package(provider: Provider, package_id: String, version_id: String, cluster_id: ClusterId, skip_compatibility: Option<bool>) -> LauncherResult<packages::Model>;
|
||||
async fn download_package(
|
||||
provider: Provider,
|
||||
package_id: String,
|
||||
version_id: String,
|
||||
cluster_id: ClusterId,
|
||||
skip_compatibility: Option<bool>,
|
||||
) -> LauncherResult<packages::Model>;
|
||||
|
||||
#[taurpc(alias = "getUsersFromAuthor")]
|
||||
async fn get_users_from_author(provider: Provider, author: PackageAuthor) -> LauncherResult<Vec<ManagedUser>>;
|
||||
async fn get_users_from_author(
|
||||
provider: Provider,
|
||||
author: PackageAuthor,
|
||||
) -> LauncherResult<Vec<ManagedUser>>;
|
||||
}
|
||||
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type, Clone)]
|
||||
pub struct CreateCluster {
|
||||
name: String,
|
||||
|
|
@ -137,7 +175,6 @@ pub struct TauriLauncherApiImpl;
|
|||
|
||||
#[taurpc::resolvers]
|
||||
impl TauriLauncherApi for TauriLauncherApiImpl {
|
||||
|
||||
// Clusters
|
||||
async fn get_clusters(self) -> LauncherResult<Vec<Cluster>> {
|
||||
api::cluster::dao::get_all_clusters().await
|
||||
|
|
@ -152,37 +189,54 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
}
|
||||
|
||||
async fn create_cluster(self, options: CreateCluster) -> LauncherResult<Cluster> {
|
||||
let cluster = api::cluster::create_cluster(&options.name, &options.mc_version, options.mc_loader, options.mc_loader_version.as_deref(), options.icon).await?;
|
||||
|
||||
api::setting_profiles::create_profile(&options.name, |mut active_model: SettingsProfilePartial| async move {
|
||||
active_model.force_fullscreen = Set(Some(false));
|
||||
|
||||
active_model.mem_max = Set(Some(2048));
|
||||
|
||||
Ok(active_model)
|
||||
})
|
||||
let cluster = api::cluster::create_cluster(
|
||||
&options.name,
|
||||
&options.mc_version,
|
||||
options.mc_loader,
|
||||
options.mc_loader_version.as_deref(),
|
||||
options.icon,
|
||||
)
|
||||
.await?;
|
||||
|
||||
api::cluster::dao::update_cluster_by_id(cluster.id, |mut active_model: ClusterPartial| async move {
|
||||
active_model.setting_profile_name = Set(Some(options.name.clone()));
|
||||
api::setting_profiles::create_profile(
|
||||
&options.name,
|
||||
|mut active_model: SettingsProfilePartial| async move {
|
||||
active_model.force_fullscreen = Set(Some(false));
|
||||
|
||||
Ok(active_model)
|
||||
})
|
||||
active_model.mem_max = Set(Some(2048));
|
||||
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
api::cluster::dao::update_cluster_by_id(
|
||||
cluster.id,
|
||||
|mut active_model: ClusterPartial| async move {
|
||||
active_model.setting_profile_name = Set(Some(options.name.clone()));
|
||||
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(cluster)
|
||||
}
|
||||
|
||||
async fn launch_cluster(self, id: ClusterId, uuid: Option<uuid::Uuid>) -> LauncherResult<()> {
|
||||
let mut cluster = api::cluster::dao::get_cluster_by_id(id).await?
|
||||
let mut cluster = api::cluster::dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?;
|
||||
|
||||
let uuid = match uuid {
|
||||
Some(uuid) => uuid,
|
||||
None => api::credentials::get_default_user().await?.ok_or_else(|| anyhow::anyhow!("no default user set"))?,
|
||||
None => api::credentials::get_default_user()
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no default user set"))?,
|
||||
};
|
||||
|
||||
let user = api::credentials::get_user(uuid).await?
|
||||
let user = api::credentials::get_user(uuid)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("user with uuid {} not found", uuid))?;
|
||||
|
||||
// let user = api::credentials::get_fake_user();
|
||||
|
|
@ -192,34 +246,42 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_cluster_by_id(self, id: ClusterId, request: ClusterUpdate) -> LauncherResult<()> {
|
||||
api::cluster::dao::update_cluster_by_id(id, |mut active_model: ClusterPartial| async move {
|
||||
if let Some(name) = request.name {
|
||||
active_model.name = Set(name)
|
||||
}
|
||||
async fn update_cluster_by_id(
|
||||
self,
|
||||
id: ClusterId,
|
||||
request: ClusterUpdate,
|
||||
) -> LauncherResult<()> {
|
||||
api::cluster::dao::update_cluster_by_id(
|
||||
id,
|
||||
|mut active_model: ClusterPartial| async move {
|
||||
if let Some(name) = request.name {
|
||||
active_model.name = Set(name);
|
||||
}
|
||||
|
||||
// ok i know this is so wrong but im not a rust guy
|
||||
if let Some(icon_url) = request.icon_url {
|
||||
api::cluster::dao::set_icon_by_id(id, &icon_url).await?;
|
||||
}
|
||||
// ok i know this is so wrong but im not a rust guy
|
||||
if let Some(icon_url) = request.icon_url {
|
||||
api::cluster::dao::set_icon_by_id(id, &icon_url).await?;
|
||||
}
|
||||
|
||||
Ok(active_model)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| LauncherError::from(e))?;
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_screenshots(self, id: ClusterId) -> LauncherResult<Vec<String>> {
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(id).await?
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?;
|
||||
|
||||
api::cluster::content::get_screenshots(&cluster).await
|
||||
}
|
||||
|
||||
async fn get_worlds(self, id: ClusterId) -> LauncherResult<Vec<String>> {
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(id).await?
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", id))?;
|
||||
|
||||
api::cluster::content::get_worlds(&cluster).await
|
||||
|
|
@ -235,42 +297,49 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
}
|
||||
|
||||
// please kill this with fire
|
||||
async fn update_cluster_profile(self, name: String, profile: ProfileUpdate) -> LauncherResult<SettingsProfile> {
|
||||
let profile = api::setting_profiles::dao::update_profile_by_name(&name, |mut active_model: SettingsProfilePartial| async move {
|
||||
if let Some(res) = profile.res {
|
||||
active_model.res = Set(Some(res));
|
||||
}
|
||||
async fn update_cluster_profile(
|
||||
self,
|
||||
name: String,
|
||||
profile: ProfileUpdate,
|
||||
) -> LauncherResult<SettingsProfile> {
|
||||
let profile = api::setting_profiles::dao::update_profile_by_name(
|
||||
&name,
|
||||
|mut active_model: SettingsProfilePartial| async move {
|
||||
if let Some(res) = profile.res {
|
||||
active_model.res = Set(Some(res));
|
||||
}
|
||||
|
||||
if let Some(force_fullscreen) = profile.force_fullscreen {
|
||||
active_model.force_fullscreen = Set(Some(force_fullscreen));
|
||||
}
|
||||
if let Some(force_fullscreen) = profile.force_fullscreen {
|
||||
active_model.force_fullscreen = Set(Some(force_fullscreen));
|
||||
}
|
||||
|
||||
if let Some(mem_max) = profile.mem_max {
|
||||
active_model.mem_max = Set(Some(mem_max));
|
||||
}
|
||||
if let Some(mem_max) = profile.mem_max {
|
||||
active_model.mem_max = Set(Some(mem_max));
|
||||
}
|
||||
|
||||
if let Some(launch_args) = profile.launch_args {
|
||||
active_model.launch_args = Set(Some(launch_args));
|
||||
}
|
||||
if let Some(launch_args) = profile.launch_args {
|
||||
active_model.launch_args = Set(Some(launch_args));
|
||||
}
|
||||
|
||||
if let Some(launch_env) = profile.launch_env {
|
||||
active_model.launch_env = Set(Some(launch_env));
|
||||
}
|
||||
if let Some(launch_env) = profile.launch_env {
|
||||
active_model.launch_env = Set(Some(launch_env));
|
||||
}
|
||||
|
||||
if let Some(hook_pre) = profile.hook_pre {
|
||||
active_model.hook_pre = Set(Some(hook_pre));
|
||||
}
|
||||
if let Some(hook_pre) = profile.hook_pre {
|
||||
active_model.hook_pre = Set(Some(hook_pre));
|
||||
}
|
||||
|
||||
if let Some(hook_wrapper) = profile.hook_wrapper {
|
||||
active_model.hook_wrapper = Set(Some(hook_wrapper));
|
||||
}
|
||||
if let Some(hook_wrapper) = profile.hook_wrapper {
|
||||
active_model.hook_wrapper = Set(Some(hook_wrapper));
|
||||
}
|
||||
|
||||
if let Some(hook_post) = profile.hook_post {
|
||||
active_model.hook_post = Set(Some(hook_post));
|
||||
}
|
||||
if let Some(hook_post) = profile.hook_post {
|
||||
active_model.hook_post = Set(Some(hook_post));
|
||||
}
|
||||
|
||||
Ok(active_model)
|
||||
})
|
||||
Ok(active_model)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(profile)
|
||||
|
|
@ -285,7 +354,6 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
api::game::metadata::get_game_versions().await
|
||||
}
|
||||
|
||||
|
||||
// Users
|
||||
async fn get_users(self) -> LauncherResult<Vec<MinecraftCredentials>> {
|
||||
api::credentials::get_users().await
|
||||
|
|
@ -299,7 +367,10 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
api::credentials::remove_user(uuid).await
|
||||
}
|
||||
|
||||
async fn get_default_user(self, fallback: Option<bool>) -> LauncherResult<Option<MinecraftCredentials>> {
|
||||
async fn get_default_user(
|
||||
self,
|
||||
fallback: Option<bool>,
|
||||
) -> LauncherResult<Option<MinecraftCredentials>> {
|
||||
let uuid = api::credentials::get_default_user().await?;
|
||||
|
||||
if fallback.is_some_and(|fallback| fallback) && uuid.is_none() {
|
||||
|
|
@ -316,7 +387,10 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
api::credentials::set_default_user(uuid).await
|
||||
}
|
||||
|
||||
async fn open_msa_login<R: Runtime>(self, app_handle: AppHandle<R>) -> LauncherResult<Option<MinecraftCredentials>> {
|
||||
async fn open_msa_login<R: Runtime>(
|
||||
self,
|
||||
app_handle: AppHandle<R>,
|
||||
) -> LauncherResult<Option<MinecraftCredentials>> {
|
||||
use tauri::Manager;
|
||||
|
||||
let flow = api::credentials::begin().await?;
|
||||
|
|
@ -336,10 +410,10 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
.map_err(|_| anyhow::anyhow!("failed to parse auth redirect url"))?,
|
||||
),
|
||||
)
|
||||
.title(format!("Login to {}", Core::get().launcher_name))
|
||||
.center()
|
||||
.focused(true)
|
||||
.build()?;
|
||||
.title(format!("Login to {}", Core::get().launcher_name))
|
||||
.center()
|
||||
.focused(true)
|
||||
.build()?;
|
||||
|
||||
win.request_user_attention(Some(tauri::UserAttentionType::Critical))?;
|
||||
|
||||
|
|
@ -352,17 +426,12 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
.url()?
|
||||
.as_str()
|
||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||
&& let Some((_, code)) = win.url()?.query_pairs().find(|x| x.0 == "code")
|
||||
{
|
||||
if let Some((_, code)) = win
|
||||
.url()?
|
||||
.query_pairs()
|
||||
.find(|x| x.0 == "code")
|
||||
{
|
||||
win.close()?;
|
||||
let value = api::credentials::finish(&code.clone(), flow).await?;
|
||||
win.close()?;
|
||||
let value = api::credentials::finish(&code.clone(), flow).await?;
|
||||
|
||||
return Ok(Some(value));
|
||||
}
|
||||
return Ok(Some(value));
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
|
|
@ -392,34 +461,66 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
}
|
||||
|
||||
// Packages
|
||||
async fn search_packages(self, provider: Provider, query: SearchQuery) -> LauncherResult<Paginated<SearchResult>> {
|
||||
Ok(provider.search(&query).await?)
|
||||
async fn search_packages(
|
||||
self,
|
||||
provider: Provider,
|
||||
query: SearchQuery,
|
||||
) -> LauncherResult<Paginated<SearchResult>> {
|
||||
provider.search(&query).await
|
||||
}
|
||||
|
||||
async fn get_package(self, provider: Provider, slug: String) -> LauncherResult<ManagedPackage> {
|
||||
Ok(provider.get(&slug).await?)
|
||||
provider.get(&slug).await
|
||||
}
|
||||
|
||||
async fn get_multiple_packages(self, provider: Provider, slugs: Vec<String>) -> LauncherResult<Vec<ManagedPackage>> {
|
||||
Ok(provider.get_multiple(&slugs).await?)
|
||||
async fn get_multiple_packages(
|
||||
self,
|
||||
provider: Provider,
|
||||
slugs: Vec<String>,
|
||||
) -> LauncherResult<Vec<ManagedPackage>> {
|
||||
provider.get_multiple(&slugs).await
|
||||
}
|
||||
|
||||
async fn get_package_versions(self, provider: Provider, slug: String, mc_versions: Option<Vec<String>>, loaders: Option<Vec<GameLoader>>, offset: usize, limit: usize) -> LauncherResult<Paginated<ManagedVersion>> {
|
||||
Ok(provider.get_versions_paginated(&slug, mc_versions, loaders, offset, limit).await?)
|
||||
async fn get_package_versions(
|
||||
self,
|
||||
provider: Provider,
|
||||
slug: String,
|
||||
mc_versions: Option<Vec<String>>,
|
||||
loaders: Option<Vec<GameLoader>>,
|
||||
offset: usize,
|
||||
limit: usize,
|
||||
) -> LauncherResult<Paginated<ManagedVersion>> {
|
||||
provider
|
||||
.get_versions_paginated(&slug, mc_versions, loaders, offset, limit)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_package_user(self, provider: Provider, slug: String) -> LauncherResult<ManagedUser> {
|
||||
Ok(provider.get_user(&slug).await?)
|
||||
async fn get_package_user(
|
||||
self,
|
||||
provider: Provider,
|
||||
slug: String,
|
||||
) -> LauncherResult<ManagedUser> {
|
||||
provider.get_user(&slug).await
|
||||
}
|
||||
|
||||
async fn download_package(self, provider: Provider, package_id: String, version_id: String, cluster_id: ClusterId, skip_compatibility: Option<bool>) -> LauncherResult<packages::Model> {
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(cluster_id).await?
|
||||
async fn download_package(
|
||||
self,
|
||||
provider: Provider,
|
||||
package_id: String,
|
||||
version_id: String,
|
||||
cluster_id: ClusterId,
|
||||
skip_compatibility: Option<bool>,
|
||||
) -> LauncherResult<packages::Model> {
|
||||
let cluster = api::cluster::dao::get_cluster_by_id(cluster_id)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("cluster with id {} not found", cluster_id))?;
|
||||
|
||||
let package = provider.get(&package_id).await?;
|
||||
|
||||
let versions = provider.get_versions(&[version_id]).await?;
|
||||
let version = versions.into_iter().next()
|
||||
let version = versions
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("Version not found"))?;
|
||||
|
||||
let model = api::packages::download_package(&package, &version, None).await?;
|
||||
|
|
@ -429,7 +530,11 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
|
|||
Ok(model)
|
||||
}
|
||||
|
||||
async fn get_users_from_author(self, provider: Provider, author: PackageAuthor) -> LauncherResult<Vec<ManagedUser>> {
|
||||
Ok(provider.get_users_from_author(author).await?)
|
||||
async fn get_users_from_author(
|
||||
self,
|
||||
provider: Provider,
|
||||
author: PackageAuthor,
|
||||
) -> LauncherResult<Vec<ManagedUser>> {
|
||||
provider.get_users_from_author(author).await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
#![feature(let_chains)]
|
||||
#![feature(slice_as_array)]
|
||||
#![allow(clippy::struct_excessive_bools)]
|
||||
|
||||
use api::proxy::LauncherProxy;
|
||||
use store::{proxy::ProxyState, semaphore::SemaphoreStore, Core, CoreOptions, Dirs, State};
|
||||
use error::LauncherResult;
|
||||
pub use logger::start_logger;
|
||||
use store::proxy::ProxyState;
|
||||
use store::semaphore::SemaphoreStore;
|
||||
use store::{Core, CoreOptions, Dirs, State};
|
||||
|
||||
pub mod api;
|
||||
pub mod constants;
|
||||
|
|
@ -15,10 +17,12 @@ pub mod utils;
|
|||
mod logger;
|
||||
|
||||
pub use onelauncher_macro::*;
|
||||
pub use onelauncher_entity as entity;
|
||||
pub use onelauncher_migration as migration;
|
||||
pub use {onelauncher_entity as entity, onelauncher_migration as migration};
|
||||
|
||||
pub async fn initialize_core(options: CoreOptions, proxy_backend: impl LauncherProxy + 'static) -> LauncherResult<()> {
|
||||
pub async fn initialize_core(
|
||||
options: CoreOptions,
|
||||
proxy_backend: impl LauncherProxy + 'static,
|
||||
) -> LauncherResult<()> {
|
||||
Core::initialize(options).await?;
|
||||
Dirs::get().await?;
|
||||
start_logger().await;
|
||||
|
|
@ -30,4 +34,4 @@ pub async fn initialize_core(options: CoreOptions, proxy_backend: impl LauncherP
|
|||
tracing::info!("Core initialized successfully");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt};
|
||||
use crate::store::Core;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
// Handling for the live development logging
|
||||
// This will log to the console, and will not log to a file
|
||||
#[cfg(debug_assertions)]
|
||||
pub async fn start_logger() {
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get().logger_filter
|
||||
.as_ref()
|
||||
.unwrap_or(
|
||||
&format!("{}=info", env!("CARGO_PKG_NAME"))
|
||||
)
|
||||
)
|
||||
});
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get()
|
||||
.logger_filter
|
||||
.clone()
|
||||
.unwrap_or_else(|| format!("{}=info", env!("CARGO_PKG_NAME"))),
|
||||
)
|
||||
});
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
|
|
@ -28,13 +27,14 @@ pub async fn start_logger() {
|
|||
fmt_layer = fmt_layer.with_span_events(span);
|
||||
}
|
||||
|
||||
if let Err(err) = tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.try_init() {
|
||||
eprintln!("Could not set default logger: {err}");
|
||||
}
|
||||
if let Err(err) = tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.try_init()
|
||||
{
|
||||
eprintln!("Could not set default logger: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
// Handling for the live production logging
|
||||
|
|
@ -42,44 +42,41 @@ pub async fn start_logger() {
|
|||
#[cfg(not(debug_assertions))]
|
||||
pub async fn start_logger() {
|
||||
use chrono::Local;
|
||||
use std::fs::OpenOptions;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
|
||||
use crate::store::Dirs;
|
||||
use crate::store::Core;
|
||||
use std::fs::OpenOptions;
|
||||
use tracing_subscriber::fmt::time::ChronoLocal;
|
||||
|
||||
// Initialize and get logs directory path
|
||||
let logs_dir = Dirs::get_launcher_logs_dir().await.unwrap_or_default();
|
||||
use crate::store::{Core, Dirs};
|
||||
|
||||
let log_file_name = format!("launcher_{}.log", Local::now().format("%Y%m%d_%H%M%S"));
|
||||
let log_file_path = logs_dir.join(log_file_name);
|
||||
// Initialize and get logs directory path
|
||||
let logs_dir = Dirs::get_launcher_logs_dir().await.unwrap_or_default();
|
||||
|
||||
if let Err(err) = std::fs::create_dir_all(&logs_dir) {
|
||||
eprintln!("Could not create logs directory: {err}");
|
||||
}
|
||||
let log_file_name = format!("launcher_{}.log", Local::now().format("%Y%m%d_%H%M%S"));
|
||||
let log_file_path = logs_dir.join(log_file_name);
|
||||
|
||||
let file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Could not start open log file: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(err) = std::fs::create_dir_all(&logs_dir) {
|
||||
eprintln!("Could not create logs directory: {err}");
|
||||
}
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get().logger_filter
|
||||
.as_ref()
|
||||
.unwrap_or(
|
||||
&format!("{}=info", env!("CARGO_PKG_NAME"))
|
||||
)
|
||||
)
|
||||
});
|
||||
let file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(&log_file_path)
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
eprintln!("Could not start open log file: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let filter = tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
tracing_subscriber::EnvFilter::new(
|
||||
Core::get()
|
||||
.logger_filter
|
||||
.as_ref()
|
||||
.unwrap_or(&format!("{}=info", env!("CARGO_PKG_NAME"))),
|
||||
)
|
||||
});
|
||||
|
||||
let mut fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.compact()
|
||||
|
|
@ -91,9 +88,9 @@ pub async fn start_logger() {
|
|||
fmt_layer = fmt_layer.with_span_events(span);
|
||||
}
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.with(tracing_error::ErrorLayer::default())
|
||||
.init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ pub enum HashAlgorithm {
|
|||
const BUFFER_SIZE: usize = 1024 * 256; // 16 KiB
|
||||
|
||||
impl HashAlgorithm {
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn hash(&self, data: &[u8]) -> Result<String, IOError> {
|
||||
let mut hasher = get_inner(self);
|
||||
hasher.update(data);
|
||||
|
|
@ -110,7 +111,7 @@ fn get_inner(algorithm: &HashAlgorithm) -> Box<dyn DynDigest + Send + Sync> {
|
|||
match algorithm {
|
||||
HashAlgorithm::Sha1 => Box::new(sha1::Sha1::default()),
|
||||
HashAlgorithm::Sha256 => Box::new(sha2::Sha256::default()),
|
||||
HashAlgorithm::Md5 => Box::new(md5::Md5::default())
|
||||
HashAlgorithm::Md5 => Box::new(md5::Md5::default()),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,4 +120,4 @@ fn to_hex(data: &[u8]) -> String {
|
|||
let _ = write!(output, "{b:02x}");
|
||||
output
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use onelauncher_entity::icon::{Icon, IconType};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::{error::LauncherResult, store::Dirs};
|
||||
use crate::error::LauncherResult;
|
||||
use crate::store::Dirs;
|
||||
|
||||
use super::{crypto::HashAlgorithm, http, io};
|
||||
use super::crypto::HashAlgorithm;
|
||||
use super::{http, io};
|
||||
|
||||
/// Caches a icon on the filesystem. Name is the equivalent to the sha1 hash of the icon.
|
||||
pub async fn cache_icon(icon: &Icon) -> LauncherResult<Icon> {
|
||||
|
|
@ -12,17 +14,16 @@ pub async fn cache_icon(icon: &Icon) -> LauncherResult<Icon> {
|
|||
IconType::Path => {
|
||||
let bytes = io::read(&**icon).await?;
|
||||
cache_icon_bytes(&bytes).await?
|
||||
},
|
||||
}
|
||||
IconType::Url => {
|
||||
let bytes = http::fetch(Method::GET, icon).await?;
|
||||
cache_icon_bytes(&bytes).await?
|
||||
},
|
||||
}
|
||||
IconType::Unknown => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"icon type is unknown, cannot cache icon: {}",
|
||||
icon
|
||||
).into());
|
||||
},
|
||||
return Err(
|
||||
anyhow::anyhow!("icon type is unknown, cannot cache icon: {}", icon).into(),
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -36,5 +37,5 @@ pub async fn cache_icon_bytes(bytes: &[u8]) -> LauncherResult<Icon> {
|
|||
let path = dir.join(&hash);
|
||||
io::write(&path, bytes).await?;
|
||||
|
||||
Ok(Icon::from_hash(hash))
|
||||
Ok(Icon::from_hash(&hash))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ use sea_orm::{DeriveActiveEnum, EnumIter};
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, EnumIter, DeriveActiveEnum)]
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, EnumIter, DeriveActiveEnum,
|
||||
)]
|
||||
#[sea_orm(rs_type = "u8", db_type = "Integer")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ClusterStage {
|
||||
|
|
@ -16,18 +18,23 @@ pub enum ClusterStage {
|
|||
}
|
||||
|
||||
impl ClusterStage {
|
||||
pub fn is_downloading(&self) -> bool {
|
||||
#[must_use]
|
||||
pub const fn is_downloading(&self) -> bool {
|
||||
matches!(self, Self::Downloading | Self::Repairing)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ClusterStage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
ClusterStage::NotReady => "Not Ready",
|
||||
ClusterStage::Downloading => "Downloading",
|
||||
ClusterStage::Repairing => "Repairing",
|
||||
ClusterStage::Ready => "Ready",
|
||||
})
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::NotReady => "Not Ready",
|
||||
Self::Downloading => "Downloading",
|
||||
Self::Repairing => "Repairing",
|
||||
Self::Ready => "Ready",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
use std::{fmt::Display, ops::Deref, str::FromStr};
|
||||
use std::fmt::Display;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use sea_orm::{sea_query::Nullable, DeriveValueType};
|
||||
use sea_orm::DeriveValueType;
|
||||
use sea_orm::sea_query::Nullable;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// #[onelauncher_macro::specta]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType, specta::Type)]
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, DeriveValueType, specta::Type,
|
||||
)]
|
||||
pub struct Icon(String);
|
||||
|
||||
impl Nullable for Icon {
|
||||
|
|
@ -14,7 +19,8 @@ impl Nullable for Icon {
|
|||
}
|
||||
|
||||
impl Icon {
|
||||
pub fn from_hash(hash: String) -> Self {
|
||||
#[must_use]
|
||||
pub fn from_hash(hash: &str) -> Self {
|
||||
Self(format!("cache://{hash}"))
|
||||
}
|
||||
|
||||
|
|
@ -27,7 +33,8 @@ impl Icon {
|
|||
Some(Self(format!("{}", path.display())))
|
||||
}
|
||||
|
||||
pub fn try_from_url(url: url::Url) -> Option<Self> {
|
||||
#[must_use]
|
||||
pub fn try_from_url(url: &url::Url) -> Option<Self> {
|
||||
if url.scheme() != "http" && url.scheme() != "https" {
|
||||
return None;
|
||||
}
|
||||
|
|
@ -35,6 +42,7 @@ impl Icon {
|
|||
Some(Self(url.as_str().to_string()))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_type(&self) -> IconType {
|
||||
if self.is_path() {
|
||||
IconType::Path
|
||||
|
|
@ -48,16 +56,19 @@ impl Icon {
|
|||
}
|
||||
|
||||
/// Returns true if the icon is a path to a file.
|
||||
#[must_use]
|
||||
pub fn is_path(&self) -> bool {
|
||||
self.0.starts_with("file://")
|
||||
}
|
||||
|
||||
/// Returns true if the icon is a HTTP URL.
|
||||
#[must_use]
|
||||
pub fn is_url(&self) -> bool {
|
||||
self.0.starts_with("http://") || self.0.starts_with("https://")
|
||||
}
|
||||
|
||||
/// Returns true if the icon is a cached icon. Relative to the cache directory.
|
||||
#[must_use]
|
||||
pub fn is_cached(&self) -> bool {
|
||||
self.0.starts_with("cache://")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
use std::{fmt::Display, str::FromStr};
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use sea_orm::{DeriveActiveEnum, EnumIter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, DeriveActiveEnum)]
|
||||
#[derive(
|
||||
Default,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
EnumIter,
|
||||
DeriveActiveEnum,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[sea_orm(rs_type = "u8", db_type = "Integer")]
|
||||
pub enum GameLoader {
|
||||
|
|
@ -19,12 +32,12 @@ pub enum GameLoader {
|
|||
}
|
||||
|
||||
impl GameLoader {
|
||||
|
||||
#[must_use]
|
||||
pub const fn is_modded(&self) -> bool {
|
||||
!matches!(self, Self::Vanilla)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn get_format_version(&self) -> usize {
|
||||
match self {
|
||||
Self::Vanilla => interpulse::api::minecraft::CURRENT_FORMAT_VERSION,
|
||||
|
|
@ -36,15 +49,17 @@ impl GameLoader {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_format_name(&self) -> String {
|
||||
match self {
|
||||
Self::Vanilla => String::from("minecraft"),
|
||||
Self::NeoForge => String::from("neo"), // TODO(metadata): change to neoforge
|
||||
_ => self.to_string().to_lowercase().replace(['_', '.', ' '], "")
|
||||
_ => self.to_string().to_lowercase().replace(['_', '.', ' '], ""),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compatible_with(&self, other: &Self) -> bool {
|
||||
#[must_use]
|
||||
pub const fn compatible_with(&self, other: &Self) -> bool {
|
||||
match self {
|
||||
Self::Vanilla => matches!(other, Self::Vanilla),
|
||||
Self::Forge => matches!(other, Self::Forge),
|
||||
|
|
@ -55,6 +70,7 @@ impl GameLoader {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn modded_loaders() -> &'static [Self] {
|
||||
&[
|
||||
Self::Forge,
|
||||
|
|
@ -64,22 +80,23 @@ impl GameLoader {
|
|||
Self::LegacyFabric,
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl TryFrom<String> for GameLoader {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(s: String) -> Result<GameLoader, String> {
|
||||
Ok(match s.to_lowercase().replace(['_', '.', ' '], "").as_str() {
|
||||
"vanilla" | "minecraft" => Self::Vanilla,
|
||||
"forge" => Self::Forge,
|
||||
"neoforge" | "neo" => Self::NeoForge,
|
||||
"quilt" => Self::Quilt,
|
||||
"fabric" => Self::Fabric,
|
||||
"legacyfabric" => Self::LegacyFabric,
|
||||
_ => return Err(format!("'{s}' is not a valid game loader")),
|
||||
})
|
||||
fn try_from(s: String) -> Result<Self, String> {
|
||||
Ok(
|
||||
match s.to_lowercase().replace(['_', '.', ' '], "").as_str() {
|
||||
"vanilla" | "minecraft" => Self::Vanilla,
|
||||
"forge" => Self::Forge,
|
||||
"neoforge" | "neo" => Self::NeoForge,
|
||||
"quilt" => Self::Quilt,
|
||||
"fabric" => Self::Fabric,
|
||||
"legacyfabric" => Self::LegacyFabric,
|
||||
_ => return Err(format!("'{s}' is not a valid game loader")),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,15 +110,13 @@ impl FromStr for GameLoader {
|
|||
|
||||
impl Display for GameLoader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(
|
||||
match self {
|
||||
Self::Vanilla => "Vanilla",
|
||||
Self::Forge => "Forge",
|
||||
Self::NeoForge => "NeoForge",
|
||||
Self::Quilt => "Quilt",
|
||||
Self::Fabric => "Fabric",
|
||||
Self::LegacyFabric => "LegacyFabric",
|
||||
}
|
||||
)
|
||||
f.write_str(match self {
|
||||
Self::Vanilla => "Vanilla",
|
||||
Self::Forge => "Forge",
|
||||
Self::NeoForge => "NeoForge",
|
||||
Self::Quilt => "Quilt",
|
||||
Self::Fabric => "Fabric",
|
||||
Self::LegacyFabric => "LegacyFabric",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ impl PackageType {
|
|||
Self::Shader => "shaders",
|
||||
Self::DataPack => "datapacks",
|
||||
Self::ModPack => "modpacks",
|
||||
}.to_string()
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ impl Display for Provider {
|
|||
|
||||
impl Provider {
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &str {
|
||||
pub const fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Modrinth => "Modrinth",
|
||||
Self::Curseforge => "Curseforge",
|
||||
|
|
@ -55,7 +56,7 @@ impl Provider {
|
|||
|
||||
/// Get the URL of the provider with a trailing slash
|
||||
#[must_use]
|
||||
pub fn website(&self) -> &str {
|
||||
pub const fn website(&self) -> &str {
|
||||
match self {
|
||||
Self::Modrinth => "https://modrinth.com/",
|
||||
Self::Curseforge => "https://curseforge.com/",
|
||||
|
|
@ -67,4 +68,4 @@ impl Provider {
|
|||
pub const fn get_providers() -> &'static [Self] {
|
||||
&[Self::Modrinth, Self::Curseforge, Self::SkyClient]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use sea_orm::{sea_query::{Nullable, ValueType}, TryGetableFromJson};
|
||||
use sea_orm::TryGetableFromJson;
|
||||
use sea_orm::sea_query::{Nullable, ValueType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
|
|
@ -15,14 +16,15 @@ impl Default for Resolution {
|
|||
}
|
||||
|
||||
impl Resolution {
|
||||
pub fn new(width: u32, height: u32) -> Self {
|
||||
#[must_use]
|
||||
pub const fn new(width: u32, height: u32) -> Self {
|
||||
Self { width, height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Resolution> for sea_orm::Value {
|
||||
fn from(value: Resolution) -> Self {
|
||||
sea_orm::Value::Json(serde_json::to_value(value).ok().map(Box::new))
|
||||
Self::Json(serde_json::to_value(value).ok().map(Box::new))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,9 +33,9 @@ impl TryGetableFromJson for Resolution {}
|
|||
impl ValueType for Resolution {
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => Ok(
|
||||
serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
|
||||
),
|
||||
sea_orm::Value::Json(Some(json)) => {
|
||||
Ok(serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?)
|
||||
}
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
|
|
@ -56,4 +58,3 @@ impl Nullable for Resolution {
|
|||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use sea_orm::{sea_query::{self, Nullable, ValueType}, TryGetableFromJson};
|
||||
use sea_orm::TryGetableFromJson;
|
||||
use sea_orm::sea_query::{self, Nullable, ValueType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
cfg_select! {
|
||||
|
|
@ -51,7 +52,7 @@ cfg_select! {
|
|||
|
||||
impl From<SettingsOsExtra> for sea_query::Value {
|
||||
fn from(value: SettingsOsExtra) -> Self {
|
||||
sea_query::Value::Json(serde_json::to_value(value).ok().map(Box::new))
|
||||
Self::Json(serde_json::to_value(value).ok().map(Box::new))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,11 +61,11 @@ impl TryGetableFromJson for SettingsOsExtra {}
|
|||
impl ValueType for SettingsOsExtra {
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => Ok(
|
||||
serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?,
|
||||
),
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
sea_orm::Value::Json(Some(json)) => {
|
||||
Ok(serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?)
|
||||
}
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name() -> String {
|
||||
|
|
@ -84,4 +85,4 @@ impl Nullable for SettingsOsExtra {
|
|||
fn null() -> sea_orm::Value {
|
||||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[onelauncher_macro::specta]
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
|
||||
|
|
@ -25,45 +26,41 @@ impl<T> sea_orm::TryGetableFromJson for DbVec<T> where for<'de> T: Deserialize<'
|
|||
|
||||
impl<T> std::convert::From<DbVec<T>> for sea_orm::Value
|
||||
where
|
||||
DbVec<T>: Serialize,
|
||||
DbVec<T>: Serialize,
|
||||
{
|
||||
fn from(source: DbVec<T>) -> Self {
|
||||
sea_orm::Value::Json(
|
||||
serde_json::to_value(&source)
|
||||
.ok()
|
||||
.map(std::boxed::Box::new),
|
||||
)
|
||||
}
|
||||
fn from(source: DbVec<T>) -> Self {
|
||||
Self::Json(serde_json::to_value(&source).ok().map(std::boxed::Box::new))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> sea_orm::sea_query::ValueType for DbVec<T>
|
||||
where
|
||||
DbVec<T>: DeserializeOwned,
|
||||
Self: DeserializeOwned,
|
||||
{
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => {
|
||||
Ok(serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?)
|
||||
}
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
fn try_from(v: sea_orm::Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> {
|
||||
match v {
|
||||
sea_orm::Value::Json(Some(json)) => {
|
||||
Ok(serde_json::from_value(*json).map_err(|_| sea_orm::sea_query::ValueTypeErr)?)
|
||||
}
|
||||
_ => Err(sea_orm::sea_query::ValueTypeErr),
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name() -> String {
|
||||
stringify!(#ident).to_owned()
|
||||
}
|
||||
fn type_name() -> String {
|
||||
stringify!(#ident).to_owned()
|
||||
}
|
||||
|
||||
fn array_type() -> sea_orm::sea_query::ArrayType {
|
||||
sea_orm::sea_query::ArrayType::Json
|
||||
}
|
||||
fn array_type() -> sea_orm::sea_query::ArrayType {
|
||||
sea_orm::sea_query::ArrayType::Json
|
||||
}
|
||||
|
||||
fn column_type() -> sea_orm::sea_query::ColumnType {
|
||||
sea_orm::sea_query::ColumnType::Json
|
||||
}
|
||||
fn column_type() -> sea_orm::sea_query::ColumnType {
|
||||
sea_orm::sea_query::ColumnType::Json
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> sea_orm::sea_query::Nullable for DbVec<T> {
|
||||
fn null() -> sea_orm::Value {
|
||||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
fn null() -> sea_orm::Value {
|
||||
sea_orm::Value::Json(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ use merge::Merge;
|
|||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{resolution::Resolution, settings::SettingsOsExtra};
|
||||
use crate::resolution::Resolution;
|
||||
use crate::settings::SettingsOsExtra;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize, Merge)]
|
||||
#[sea_orm(table_name = "setting_profiles")]
|
||||
|
|
@ -31,7 +32,7 @@ pub struct Model {
|
|||
#[sea_orm(nullable)]
|
||||
pub hook_post: Option<String>,
|
||||
#[sea_orm(nullable)]
|
||||
pub os_extra: Option<SettingsOsExtra>
|
||||
pub os_extra: Option<SettingsOsExtra>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
@ -65,16 +66,21 @@ impl ActiveModelBehavior for ActiveModel {}
|
|||
const GLOBAL_NAME: &str = "Global";
|
||||
|
||||
impl ActiveModel {
|
||||
#[must_use]
|
||||
pub fn is_global(&self) -> bool {
|
||||
self.name.try_as_ref().map(|name| name == GLOBAL_NAME).unwrap_or(false)
|
||||
self.name
|
||||
.try_as_ref()
|
||||
.is_some_and(|name| name == GLOBAL_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
#[must_use]
|
||||
pub fn is_global(&self) -> bool {
|
||||
self.name == GLOBAL_NAME
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn default_global_profile() -> Self {
|
||||
Self {
|
||||
name: GLOBAL_NAME.into(),
|
||||
|
|
@ -87,7 +93,7 @@ impl Model {
|
|||
launch_env: None,
|
||||
mem_max: Some(2048),
|
||||
res: None,
|
||||
os_extra: Some(SettingsOsExtra::default())
|
||||
os_extra: Some(SettingsOsExtra::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ mod pin;
|
|||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
#[cfg(feature = "specta")]
|
||||
pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn specta(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
specta::specta(attr, item)
|
||||
}
|
||||
|
||||
|
|
@ -36,12 +39,18 @@ pub fn serialized_error_derive(item: proc_macro::TokenStream) -> proc_macro::Tok
|
|||
}
|
||||
|
||||
#[proc_macro_attribute]
|
||||
pub fn error(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn error(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
error::error_attr(attr, item)
|
||||
}
|
||||
|
||||
/// This macro Box::Pin a function
|
||||
/// This macro `Box::Pin` a function
|
||||
#[proc_macro_attribute]
|
||||
pub fn pin(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn pin(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
pin::pin(attr, item)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,23 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{meta::ParseNestedMeta, parse_macro_input, Item};
|
||||
use syn::meta::ParseNestedMeta;
|
||||
use syn::{Item, parse_macro_input};
|
||||
|
||||
pub fn specta(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
pub fn specta(
|
||||
attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let mut attrs = SpectaAttributes::default();
|
||||
let parser = syn::meta::parser(|meta| attrs.parse(meta));
|
||||
let parser = syn::meta::parser(|meta| attrs.parse(&meta));
|
||||
parse_macro_input!(attr with parser);
|
||||
|
||||
let input = parse_macro_input!(item as Item);
|
||||
|
||||
let specta_rename = if let Some(rename) = attrs.rename {
|
||||
quote! {
|
||||
#[cfg_attr(feature = "specta", specta(rename = #rename))]
|
||||
}
|
||||
} else {
|
||||
TokenStream::new()
|
||||
};
|
||||
let specta_rename = attrs.rename.map_or_else(TokenStream::new, |rename| {
|
||||
quote! {
|
||||
#[cfg_attr(feature = "specta", specta(rename = #rename))]
|
||||
}
|
||||
});
|
||||
|
||||
let expanded = quote! {
|
||||
#[cfg_attr(feature = "specta", derive(specta::Type))]
|
||||
|
|
@ -32,10 +34,10 @@ struct SpectaAttributes {
|
|||
}
|
||||
|
||||
impl SpectaAttributes {
|
||||
fn parse(&mut self, meta: ParseNestedMeta) -> syn::Result<()> {
|
||||
fn parse(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> {
|
||||
if meta.path.is_ident("rename") {
|
||||
self.rename = Some(meta.value()?.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ interface MappedCategoryItem {
|
|||
id: string;
|
||||
}
|
||||
|
||||
type MappingList = Record<string, MappedCategoryItem[]>;
|
||||
type MappingList = Record<string, Array<MappedCategoryItem>>;
|
||||
|
||||
const packageTypes = ['mod', 'modpack', 'shader', 'resourcepack'] as const;
|
||||
type PackageType = typeof packageTypes[number];
|
||||
|
|
@ -43,7 +43,7 @@ async function fetchCategoriesCurseforge(packageType: PackageType): Promise<Mapp
|
|||
parentCategoryId?: number;
|
||||
};
|
||||
|
||||
const data = (await response.json() as { data: CFCategory[] }).data;
|
||||
const data = (await response.json() as { data: Array<CFCategory> }).data;
|
||||
|
||||
// TODO: Add more calssIds
|
||||
const classIds: Record<PackageType, number> = {
|
||||
|
|
@ -79,7 +79,7 @@ async function fetchCategoriesModrinth(packageType: PackageType): Promise<Mappin
|
|||
header: string;
|
||||
}
|
||||
|
||||
const data = await response.json() as ModrinthCategory[];
|
||||
const data = await response.json() as Array<ModrinthCategory>;
|
||||
|
||||
const mapping: MappingList = {};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
"scripts": {
|
||||
"tauri": "pnpm tsx tauri.ts",
|
||||
"prep": "pnpm tsx prepare.ts",
|
||||
"prepare": "pnpm tsx prepare.ts",
|
||||
"setup-win": "./setup.ps1",
|
||||
"setup-nix": "./setup.sh",
|
||||
"autoformat": "./format.sh",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { exec } from 'node:child_process';
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import process from 'node:process';
|
||||
import { consola } from 'consola';
|
||||
import mustache from 'mustache';
|
||||
import { join } from 'pathe';
|
||||
|
|
@ -77,3 +79,22 @@ catch (error) {
|
|||
consola.error(error);
|
||||
env.__exit(1);
|
||||
}
|
||||
|
||||
consola.start('building the web_commons package...');
|
||||
exec('pnpm -w web_common build', {
|
||||
cwd: env.__root,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
}, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
consola.error(`web_commons build failed: ${error.message}`);
|
||||
env.__exit(1);
|
||||
}
|
||||
if (stderr)
|
||||
consola.error(stderr);
|
||||
|
||||
consola.success('web_commons package built successfully!');
|
||||
env.__exit(0);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ if (cargoConfig.env && typeof cargoConfig.env === 'object')
|
|||
|
||||
function targetFilter(filter: 'b' | 't') {
|
||||
const filters = filter === 'b' ? ['-b', '--bundles'] : ['-t', '--target'];
|
||||
return (_: string, idx: number, args: string[]) => {
|
||||
return (_: string, idx: number, args: Array<string>) => {
|
||||
if (idx === 0)
|
||||
return false;
|
||||
const prev = args[idx - 1] ?? '';
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ export async function which(cmd: TemplateStringsArray): Promise<boolean> {
|
|||
return type() === 'Windows_NT'
|
||||
? where(cmd[0]!)
|
||||
: Promise.any(
|
||||
Array.from(new Set(process.env.PATH?.split(':')))
|
||||
.map(p => fs.access(join(p, cmd[0]!), fs.constants.X_OK)),
|
||||
).then(_ => true, _ => false);
|
||||
Array.from(new Set(process.env.PATH?.split(':')))
|
||||
.map(p => fs.access(join(p, cmd[0]!), fs.constants.X_OK)),
|
||||
).then(_ => true, _ => false);
|
||||
}
|
||||
|
||||
export async function awaitLock(file: string): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export async function tauriUpdateKey(env: CheckedEnvironment): Promise<string |
|
|||
return keys.publicKey;
|
||||
}
|
||||
|
||||
export async function patchTauri(env: CheckedEnvironment, targets: string[], args: string[]): Promise<string[]> {
|
||||
export async function patchTauri(env: CheckedEnvironment, targets: Array<string>, args: Array<string>): Promise<Array<string>> {
|
||||
consola.start('patching tauri configuration...');
|
||||
if (args.findIndex(a => ['--config', '-c'].includes(a)) !== -1)
|
||||
throw new Error('custom tauri build configuration is not supported!');
|
||||
|
|
@ -55,7 +55,7 @@ export async function patchTauri(env: CheckedEnvironment, targets: string[], arg
|
|||
const osType = type();
|
||||
const tauriPatch: {
|
||||
build: {
|
||||
features: string[];
|
||||
features: Array<string>;
|
||||
};
|
||||
bundle: {
|
||||
macOS: { minimumSystemVersion: string };
|
||||
|
|
@ -107,10 +107,10 @@ export async function patchTauri(env: CheckedEnvironment, targets: string[], arg
|
|||
if (
|
||||
(targets.includes('aarch64-apple-darwin')
|
||||
|| (targets.length === 0 && process.arch === 'arm64'))
|
||||
&& (macOSStore.minimumVersion == null || semver.lt(
|
||||
semver.coerce(macOSStore.minimumVersion)!,
|
||||
semver.coerce(macOSStore.defaultArm64)!,
|
||||
))
|
||||
&& (macOSStore.minimumVersion == null || semver.lt(
|
||||
semver.coerce(macOSStore.minimumVersion)!,
|
||||
semver.coerce(macOSStore.defaultArm64)!,
|
||||
))
|
||||
) {
|
||||
macOSStore.minimumVersion = macOSStore.defaultArm64;
|
||||
consola.debug(`setting minimum macOS version to ${macOSStore.minimumVersion}...`);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
[package]
|
||||
name = "serde_json_path_to_error"
|
||||
version = "0.2.0"
|
||||
authors = [ "Polyfrost", "Ethan Brierley <ethanboxx@gmail.com>" ]
|
||||
authors = [
|
||||
"Polyfrost",
|
||||
"Ethan Brierley <ethanboxx@gmail.com>"
|
||||
]
|
||||
license = "MIT/Apache-2.0"
|
||||
readme = "README.md"
|
||||
description = """
|
||||
A drop in replacement for serde_json where detailed errors are the default
|
||||
"""
|
||||
keywords = [ "serde", "json", "serialization" ]
|
||||
keywords = [
|
||||
"serde",
|
||||
"json",
|
||||
"serialization"
|
||||
]
|
||||
documentation = "https://docs.rs/serde_json_path_to_error/"
|
||||
repository = "https://github.com/eopb/serde_json_path_to_error"
|
||||
edition = "2024"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable eslint-comments/require-description -- i trust that one github discussion user on this */
|
||||
/* eslint-disable react/no-unstable-default-props */
|
||||
|
||||
// Taken from https://github.com/TanStack/router/discussions/823#discussioncomment-12765059
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { JSX, ReactNode } from 'react';
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import type { JSX } from 'react';
|
||||
import React, { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
interface SelectListContextType {
|
||||
onSelect: (value: string) => void;
|
||||
|
|
@ -30,7 +30,7 @@ export const SelectList: React.FC<SelectListProps> & { Item: React.FC<SelectList
|
|||
multiple = false,
|
||||
children,
|
||||
}) => {
|
||||
const handleSelect = (itemValue: string): void => {
|
||||
const handleSelect = useCallback((itemValue: string): void => {
|
||||
if (multiple) {
|
||||
const currentValues = Array.isArray(value) ? value : [];
|
||||
const newValues = currentValues.includes(itemValue)
|
||||
|
|
@ -41,20 +41,20 @@ export const SelectList: React.FC<SelectListProps> & { Item: React.FC<SelectList
|
|||
else {
|
||||
onChange(itemValue);
|
||||
}
|
||||
};
|
||||
}, [multiple, onChange, value]);
|
||||
|
||||
const isSelected = (itemValue: string): boolean => {
|
||||
const isSelected = useCallback((itemValue: string): boolean => {
|
||||
if (multiple)
|
||||
return Array.isArray(value) && value.includes(itemValue);
|
||||
|
||||
return value === itemValue;
|
||||
};
|
||||
}, [multiple, value]);
|
||||
|
||||
const contextValue: SelectListContextType = {
|
||||
const contextValue: SelectListContextType = useMemo(() => ({
|
||||
onSelect: handleSelect,
|
||||
isSelected,
|
||||
multiple,
|
||||
};
|
||||
}), [handleSelect, isSelected, multiple]);
|
||||
|
||||
return (
|
||||
<SelectListContext.Provider value={contextValue}>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default defineConfig({
|
|||
rollupOptions: {
|
||||
external: [
|
||||
'react',
|
||||
'react/jsx-runtime',
|
||||
// 'react/jsx-runtime',
|
||||
'react-dom',
|
||||
'react-aria-components',
|
||||
'motion',
|
||||
|
|
|
|||
333
pnpm-lock.yaml
generated
333
pnpm-lock.yaml
generated
|
|
@ -96,6 +96,9 @@ catalogs:
|
|||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.1.0
|
||||
react-just-parallax:
|
||||
specifier: ^3.1.16
|
||||
version: 3.1.16
|
||||
rimraf:
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.1
|
||||
|
|
@ -161,6 +164,12 @@ importers:
|
|||
eslint:
|
||||
specifier: 'catalog:'
|
||||
version: 9.25.1(jiti@2.4.2)
|
||||
husky:
|
||||
specifier: ^9.1.7
|
||||
version: 9.1.7
|
||||
lint-staged:
|
||||
specifier: ^16.1.2
|
||||
version: 16.1.2
|
||||
rimraf:
|
||||
specifier: 'catalog:'
|
||||
version: 6.0.1
|
||||
|
|
@ -172,7 +181,7 @@ importers:
|
|||
version: 5.8.3
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
.github/actions/publish-artifacts:
|
||||
dependencies:
|
||||
|
|
@ -216,7 +225,7 @@ importers:
|
|||
version: link:../../../packages/web_common
|
||||
'@tailwindcss/vite':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@tanstack/match-sorter-utils':
|
||||
specifier: 'catalog:'
|
||||
version: 8.19.4
|
||||
|
|
@ -237,7 +246,7 @@ importers:
|
|||
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@tanstack/router-plugin':
|
||||
specifier: 'catalog:'
|
||||
version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@tauri-apps/api':
|
||||
specifier: 'catalog:'
|
||||
version: 2.5.0
|
||||
|
|
@ -272,7 +281,7 @@ importers:
|
|||
specifier: 'catalog:'
|
||||
version: 19.1.0(react@19.1.0)
|
||||
react-just-parallax:
|
||||
specifier: ^3.1.16
|
||||
specifier: 'catalog:'
|
||||
version: 3.1.16(react@19.1.0)
|
||||
tailwind-merge:
|
||||
specifier: 'catalog:'
|
||||
|
|
@ -307,16 +316,16 @@ importers:
|
|||
version: 19.1.3(@types/react@19.1.2)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 'catalog:'
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.8.3
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vite-plugin-svgr:
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.3.0(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
|
||||
apps/onelauncher/desktop:
|
||||
dependencies:
|
||||
|
|
@ -338,7 +347,7 @@ importers:
|
|||
version: link:../../../packages/web_common
|
||||
'@tailwindcss/vite':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@tanstack/match-sorter-utils':
|
||||
specifier: 'catalog:'
|
||||
version: 8.19.4
|
||||
|
|
@ -359,7 +368,7 @@ importers:
|
|||
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@tanstack/router-plugin':
|
||||
specifier: 'catalog:'
|
||||
version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@tauri-apps/api':
|
||||
specifier: 'catalog:'
|
||||
version: 2.5.0
|
||||
|
|
@ -441,13 +450,13 @@ importers:
|
|||
version: 19.1.3(@types/react@19.1.2)
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 'catalog:'
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.8.3
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
packages/core: {}
|
||||
|
||||
|
|
@ -530,25 +539,25 @@ importers:
|
|||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: 'catalog:'
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
'@types/react':
|
||||
specifier: 'catalog:'
|
||||
version: 19.1.2
|
||||
'@vitejs/plugin-react':
|
||||
specifier: 'catalog:'
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.8.3
|
||||
vite:
|
||||
specifier: 'catalog:'
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
version: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vite-plugin-dts:
|
||||
specifier: 'catalog:'
|
||||
version: 4.5.4(@types/node@22.16.5)(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 4.5.4(@types/node@22.16.5)(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
vite-plugin-static-copy:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.0(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
version: 3.0.0(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
|
||||
packages:
|
||||
|
||||
|
|
@ -3159,6 +3168,7 @@ packages:
|
|||
|
||||
'@zod/mini@4.0.0-beta.20250430T185432':
|
||||
resolution: {integrity: sha512-PLamkZ3BPeUXAYUhU5CaLY6ZaAQ4PVSoy2bh54UWXPx7fCC/+vWicLmgAt8+lwjaqpe2AW8FwiLXYoCb1UIJxA==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
abort-controller@3.0.0:
|
||||
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
|
||||
|
|
@ -3211,6 +3221,10 @@ packages:
|
|||
alien-signals@0.4.14:
|
||||
resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ansi-regex@5.0.1:
|
||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -3369,6 +3383,10 @@ packages:
|
|||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chalk@5.4.1:
|
||||
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
|
||||
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
|
||||
|
||||
character-entities-html4@2.1.0:
|
||||
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
|
||||
|
||||
|
|
@ -3405,6 +3423,14 @@ packages:
|
|||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
cli-truncate@4.0.0:
|
||||
resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
client-only@0.0.1:
|
||||
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
|
||||
|
||||
|
|
@ -3419,6 +3445,9 @@ packages:
|
|||
color-name@1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
colorette@2.0.20:
|
||||
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -3426,6 +3455,10 @@ packages:
|
|||
comma-separated-tokens@2.0.3:
|
||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||
|
||||
commander@14.0.0:
|
||||
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@6.2.1:
|
||||
resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
|
@ -3534,6 +3567,15 @@ packages:
|
|||
supports-color:
|
||||
optional: true
|
||||
|
||||
debug@4.4.1:
|
||||
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decimal.js@10.4.3:
|
||||
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
|
||||
|
||||
|
|
@ -3591,6 +3633,9 @@ packages:
|
|||
electron-to-chromium@1.5.149:
|
||||
resolution: {integrity: sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ==}
|
||||
|
||||
emoji-regex@10.4.0:
|
||||
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
|
|
@ -3605,6 +3650,10 @@ packages:
|
|||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
environment@1.1.0:
|
||||
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
error-ex@1.3.2:
|
||||
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
|
||||
|
||||
|
|
@ -3905,6 +3954,9 @@ packages:
|
|||
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
eventemitter3@5.0.1:
|
||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||
|
||||
events@3.3.0:
|
||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
||||
engines: {node: '>=0.8.x'}
|
||||
|
|
@ -4035,6 +4087,10 @@ packages:
|
|||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
get-east-asian-width@1.3.0:
|
||||
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-stream@9.0.1:
|
||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4155,6 +4211,11 @@ packages:
|
|||
resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
|
||||
husky@9.1.7:
|
||||
resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -4233,6 +4294,14 @@ packages:
|
|||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
is-fullwidth-code-point@4.0.0:
|
||||
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-fullwidth-code-point@5.0.0:
|
||||
resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -4444,9 +4513,22 @@ packages:
|
|||
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lilconfig@3.1.3:
|
||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
lint-staged@16.1.2:
|
||||
resolution: {integrity: sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==}
|
||||
engines: {node: '>=20.17'}
|
||||
hasBin: true
|
||||
|
||||
listr2@8.3.3:
|
||||
resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
local-pkg@1.1.1:
|
||||
resolution: {integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==}
|
||||
engines: {node: '>=14'}
|
||||
|
|
@ -4462,6 +4544,9 @@ packages:
|
|||
lodash.castarray@4.4.0:
|
||||
resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==}
|
||||
|
||||
lodash.debounce@4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
|
||||
lodash.isplainobject@4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
|
||||
|
|
@ -4471,6 +4556,10 @@ packages:
|
|||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
log-update@6.1.0:
|
||||
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
longest-streak@3.1.0:
|
||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||
|
||||
|
|
@ -4655,6 +4744,10 @@ packages:
|
|||
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
mimic-function@5.0.1:
|
||||
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
min-indent@1.0.1:
|
||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
|
|
@ -4734,6 +4827,10 @@ packages:
|
|||
resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==}
|
||||
hasBin: true
|
||||
|
||||
nano-spawn@1.0.2:
|
||||
resolution: {integrity: sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==}
|
||||
engines: {node: '>=20.17'}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
|
|
@ -4786,6 +4883,10 @@ packages:
|
|||
once@1.4.0:
|
||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
|
||||
onetime@7.0.0:
|
||||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -4914,6 +5015,11 @@ packages:
|
|||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pidtree@0.6.0:
|
||||
resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
|
||||
engines: {node: '>=0.10'}
|
||||
hasBin: true
|
||||
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
|
|
@ -5098,10 +5204,17 @@ packages:
|
|||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
reusify@1.0.4:
|
||||
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
|
||||
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rimraf@6.0.1:
|
||||
resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
|
||||
engines: {node: 20 || >=22}
|
||||
|
|
@ -5198,6 +5311,14 @@ packages:
|
|||
sisteransi@1.0.5:
|
||||
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
slice-ansi@7.1.0:
|
||||
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
smol-toml@1.3.0:
|
||||
resolution: {integrity: sha512-tWpi2TsODPScmi48b/OQZGi2lgUmBCHy6SZrhi/FdnnHiU1GwebbCfuQuxsC3nHaLwtYeJGPrDZDIeodDOc4pA==}
|
||||
engines: {node: '>= 18'}
|
||||
|
|
@ -5269,6 +5390,10 @@ packages:
|
|||
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
string-width@7.2.0:
|
||||
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
string_decoder@1.1.1:
|
||||
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
|
||||
|
||||
|
|
@ -5808,6 +5933,10 @@ packages:
|
|||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
wrap-ansi@9.0.0:
|
||||
resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
|
||||
|
|
@ -5853,6 +5982,11 @@ packages:
|
|||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
yaml@2.8.0:
|
||||
resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
yocto-queue@0.1.0:
|
||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||
engines: {node: '>=10'}
|
||||
|
|
@ -8355,12 +8489,12 @@ snapshots:
|
|||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 4.1.10
|
||||
|
||||
'@tailwindcss/vite@4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))':
|
||||
'@tailwindcss/vite@4.1.10(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.10
|
||||
'@tailwindcss/oxide': 4.1.10
|
||||
tailwindcss: 4.1.10
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
'@tanstack/eslint-config@0.1.0(eslint@9.25.1(jiti@2.4.2))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
|
|
@ -8467,7 +8601,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@tanstack/react-router': 1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
||||
'@tanstack/router-plugin@1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))':
|
||||
'@tanstack/router-plugin@1.119.0(@tanstack/react-router@1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)))(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.1)
|
||||
|
|
@ -8488,8 +8622,8 @@ snapshots:
|
|||
zod: 3.25.27
|
||||
optionalDependencies:
|
||||
'@tanstack/react-router': 1.119.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite-plugin-solid: 2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vite-plugin-solid: 2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -8788,14 +8922,14 @@ snapshots:
|
|||
|
||||
'@vercel/ncc@0.38.2': {}
|
||||
|
||||
'@vitejs/plugin-react@4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))':
|
||||
'@vitejs/plugin-react@4.4.1(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.1)
|
||||
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.27.1)
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -8970,6 +9104,10 @@ snapshots:
|
|||
|
||||
alien-signals@0.4.14: {}
|
||||
|
||||
ansi-escapes@7.0.0:
|
||||
dependencies:
|
||||
environment: 1.1.0
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
ansi-regex@6.1.0: {}
|
||||
|
|
@ -9138,6 +9276,8 @@ snapshots:
|
|||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chalk@5.4.1: {}
|
||||
|
||||
character-entities-html4@2.1.0: {}
|
||||
|
||||
character-entities-legacy@3.0.0: {}
|
||||
|
|
@ -9174,6 +9314,15 @@ snapshots:
|
|||
dependencies:
|
||||
escape-string-regexp: 1.0.5
|
||||
|
||||
cli-cursor@5.0.0:
|
||||
dependencies:
|
||||
restore-cursor: 5.1.0
|
||||
|
||||
cli-truncate@4.0.0:
|
||||
dependencies:
|
||||
slice-ansi: 5.0.0
|
||||
string-width: 7.2.0
|
||||
|
||||
client-only@0.0.1: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
|
@ -9184,6 +9333,8 @@ snapshots:
|
|||
|
||||
color-name@1.1.4: {}
|
||||
|
||||
colorette@2.0.20: {}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
|
@ -9191,6 +9342,8 @@ snapshots:
|
|||
|
||||
comma-separated-tokens@2.0.3: {}
|
||||
|
||||
commander@14.0.0: {}
|
||||
|
||||
commander@6.2.1: {}
|
||||
|
||||
comment-parser@1.4.1: {}
|
||||
|
|
@ -9278,6 +9431,10 @@ snapshots:
|
|||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decimal.js@10.4.3: {}
|
||||
|
||||
decode-named-character-reference@1.1.0:
|
||||
|
|
@ -9323,6 +9480,8 @@ snapshots:
|
|||
|
||||
electron-to-chromium@1.5.149: {}
|
||||
|
||||
emoji-regex@10.4.0: {}
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
emoji-regex@9.2.2: {}
|
||||
|
|
@ -9334,6 +9493,8 @@ snapshots:
|
|||
|
||||
entities@4.5.0: {}
|
||||
|
||||
environment@1.1.0: {}
|
||||
|
||||
error-ex@1.3.2:
|
||||
dependencies:
|
||||
is-arrayish: 0.2.1
|
||||
|
|
@ -9853,6 +10014,8 @@ snapshots:
|
|||
|
||||
event-target-shim@5.0.1: {}
|
||||
|
||||
eventemitter3@5.0.1: {}
|
||||
|
||||
events@3.3.0: {}
|
||||
|
||||
execa@9.4.1:
|
||||
|
|
@ -9982,6 +10145,8 @@ snapshots:
|
|||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-east-asian-width@1.3.0: {}
|
||||
|
||||
get-stream@9.0.1:
|
||||
dependencies:
|
||||
'@sec-ant/readable-stream': 0.4.1
|
||||
|
|
@ -10156,6 +10321,8 @@ snapshots:
|
|||
|
||||
human-signals@8.0.0: {}
|
||||
|
||||
husky@9.1.7: {}
|
||||
|
||||
iconv-lite@0.6.3:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
|
@ -10226,6 +10393,12 @@ snapshots:
|
|||
|
||||
is-fullwidth-code-point@3.0.0: {}
|
||||
|
||||
is-fullwidth-code-point@4.0.0: {}
|
||||
|
||||
is-fullwidth-code-point@5.0.0:
|
||||
dependencies:
|
||||
get-east-asian-width: 1.3.0
|
||||
|
||||
is-glob@4.0.3:
|
||||
dependencies:
|
||||
is-extglob: 2.1.1
|
||||
|
|
@ -10417,8 +10590,34 @@ snapshots:
|
|||
lightningcss-win32-arm64-msvc: 1.30.1
|
||||
lightningcss-win32-x64-msvc: 1.30.1
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
lint-staged@16.1.2:
|
||||
dependencies:
|
||||
chalk: 5.4.1
|
||||
commander: 14.0.0
|
||||
debug: 4.4.1
|
||||
lilconfig: 3.1.3
|
||||
listr2: 8.3.3
|
||||
micromatch: 4.0.8
|
||||
nano-spawn: 1.0.2
|
||||
pidtree: 0.6.0
|
||||
string-argv: 0.3.2
|
||||
yaml: 2.8.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
listr2@8.3.3:
|
||||
dependencies:
|
||||
cli-truncate: 4.0.0
|
||||
colorette: 2.0.20
|
||||
eventemitter3: 5.0.1
|
||||
log-update: 6.1.0
|
||||
rfdc: 1.4.1
|
||||
wrap-ansi: 9.0.0
|
||||
|
||||
local-pkg@1.1.1:
|
||||
dependencies:
|
||||
mlly: 1.7.4
|
||||
|
|
@ -10435,12 +10634,22 @@ snapshots:
|
|||
|
||||
lodash.castarray@4.4.0: {}
|
||||
|
||||
lodash.debounce@4.0.8: {}
|
||||
|
||||
lodash.isplainobject@4.0.6: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
log-update@6.1.0:
|
||||
dependencies:
|
||||
ansi-escapes: 7.0.0
|
||||
cli-cursor: 5.0.0
|
||||
slice-ansi: 7.1.0
|
||||
strip-ansi: 7.1.0
|
||||
wrap-ansi: 9.0.0
|
||||
|
||||
longest-streak@3.1.0: {}
|
||||
|
||||
loupe@3.1.2:
|
||||
|
|
@ -10850,6 +11059,8 @@ snapshots:
|
|||
mime-db: 1.52.0
|
||||
optional: true
|
||||
|
||||
mimic-function@5.0.1: {}
|
||||
|
||||
min-indent@1.0.1: {}
|
||||
|
||||
minimatch@10.0.1:
|
||||
|
|
@ -10916,6 +11127,8 @@ snapshots:
|
|||
|
||||
mustache@4.2.0: {}
|
||||
|
||||
nano-spawn@1.0.2: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
napi-postinstall@0.2.3: {}
|
||||
|
|
@ -10959,6 +11172,10 @@ snapshots:
|
|||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
|
||||
onetime@7.0.0:
|
||||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
|
|
@ -11078,6 +11295,8 @@ snapshots:
|
|||
|
||||
picomatch@4.0.2: {}
|
||||
|
||||
pidtree@0.6.0: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
dependencies:
|
||||
confbox: 0.1.8
|
||||
|
|
@ -11218,6 +11437,13 @@ snapshots:
|
|||
react: 19.1.0
|
||||
scheduler: 0.26.0
|
||||
|
||||
react-just-parallax@3.1.16(react@19.1.0):
|
||||
dependencies:
|
||||
framesync: 6.1.0
|
||||
lodash.debounce: 4.0.8
|
||||
prefix: 1.0.0
|
||||
react: 19.1.0
|
||||
|
||||
react-markdown@10.1.0(@types/react@19.1.2)(react@19.1.0):
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
|
|
@ -11381,8 +11607,15 @@ snapshots:
|
|||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
dependencies:
|
||||
onetime: 7.0.0
|
||||
signal-exit: 4.1.0
|
||||
|
||||
reusify@1.0.4: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rimraf@6.0.1:
|
||||
dependencies:
|
||||
glob: 11.0.0
|
||||
|
|
@ -11512,6 +11745,16 @@ snapshots:
|
|||
|
||||
sisteransi@1.0.5: {}
|
||||
|
||||
slice-ansi@5.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 4.0.0
|
||||
|
||||
slice-ansi@7.1.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
is-fullwidth-code-point: 5.0.0
|
||||
|
||||
smol-toml@1.3.0: {}
|
||||
|
||||
snake-case@3.0.4:
|
||||
|
|
@ -11594,6 +11837,12 @@ snapshots:
|
|||
emoji-regex: 9.2.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
string-width@7.2.0:
|
||||
dependencies:
|
||||
emoji-regex: 10.4.0
|
||||
get-east-asian-width: 1.3.0
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
string_decoder@1.1.1:
|
||||
dependencies:
|
||||
safe-buffer: 5.1.2
|
||||
|
|
@ -11948,7 +12197,7 @@ snapshots:
|
|||
vite-node@2.1.3(@types/node@22.16.5)(lightningcss@1.30.1)(sass@1.80.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
pathe: 1.1.2
|
||||
vite: 5.4.10(@types/node@22.16.5)(lightningcss@1.30.1)(sass@1.80.3)
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -11963,7 +12212,7 @@ snapshots:
|
|||
- terser
|
||||
optional: true
|
||||
|
||||
vite-plugin-dts@4.5.4(@types/node@22.16.5)(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)):
|
||||
vite-plugin-dts@4.5.4(@types/node@22.16.5)(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@microsoft/api-extractor': 7.52.8(@types/node@22.16.5)
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.41.1)
|
||||
|
|
@ -11976,13 +12225,13 @@ snapshots:
|
|||
magic-string: 0.30.17
|
||||
typescript: 5.8.3
|
||||
optionalDependencies:
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)):
|
||||
vite-plugin-solid@2.11.6(solid-js@1.9.6)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@babel/core': 7.27.1
|
||||
'@types/babel__core': 7.20.5
|
||||
|
|
@ -11990,27 +12239,27 @@ snapshots:
|
|||
merge-anything: 5.1.7
|
||||
solid-js: 1.9.6
|
||||
solid-refresh: 0.6.3(solid-js@1.9.6)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vitefu: 1.0.6(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0))
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
vitefu: 1.0.6(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
vite-plugin-static-copy@3.0.0(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)):
|
||||
vite-plugin-static-copy@3.0.0(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
fs-extra: 11.3.0
|
||||
p-map: 7.0.3
|
||||
picocolors: 1.1.1
|
||||
tinyglobby: 0.2.13
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-svgr@4.3.0(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)):
|
||||
vite-plugin-svgr@4.3.0(rollup@4.41.1)(typescript@5.8.3)(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.41.1)
|
||||
'@svgr/core': 8.1.0(typescript@5.8.3)
|
||||
'@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3))
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
|
@ -12028,7 +12277,7 @@ snapshots:
|
|||
sass: 1.80.3
|
||||
optional: true
|
||||
|
||||
vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0):
|
||||
vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.3
|
||||
fdir: 6.4.4(picomatch@4.0.2)
|
||||
|
|
@ -12043,11 +12292,11 @@ snapshots:
|
|||
lightningcss: 1.30.1
|
||||
sass: 1.80.3
|
||||
tsx: 4.19.4
|
||||
yaml: 2.6.0
|
||||
yaml: 2.8.0
|
||||
|
||||
vitefu@1.0.6(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)):
|
||||
vitefu@1.0.6(vite@6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)):
|
||||
optionalDependencies:
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.6.0)
|
||||
vite: 6.3.4(@types/node@22.16.5)(jiti@2.4.2)(lightningcss@1.30.1)(sass@1.80.3)(tsx@4.19.4)(yaml@2.8.0)
|
||||
optional: true
|
||||
|
||||
vitest@2.1.3(@types/node@22.16.5)(@vitest/ui@2.1.3)(jsdom@25.0.1)(lightningcss@1.30.1)(sass@1.80.3):
|
||||
|
|
@ -12060,7 +12309,7 @@ snapshots:
|
|||
'@vitest/spy': 2.1.3
|
||||
'@vitest/utils': 2.1.3
|
||||
chai: 5.1.2
|
||||
debug: 4.4.0
|
||||
debug: 4.4.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 1.1.2
|
||||
std-env: 3.7.0
|
||||
|
|
@ -12159,6 +12408,12 @@ snapshots:
|
|||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
wrap-ansi@9.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 6.2.1
|
||||
string-width: 7.2.0
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
wrappy@1.0.2: {}
|
||||
|
||||
ws@8.18.0:
|
||||
|
|
@ -12185,6 +12440,8 @@ snapshots:
|
|||
|
||||
yaml@2.6.0: {}
|
||||
|
||||
yaml@2.8.0: {}
|
||||
|
||||
yocto-queue@0.1.0: {}
|
||||
|
||||
yoctocolors@2.1.1: {}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
channel = "nightly-2025-07-23"
|
||||
profile = "minimal"
|
||||
components = [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"llvm-tools-preview",
|
||||
"rustc",
|
||||
"rust-src",
|
||||
"rustfmt",
|
||||
"rust-docs",
|
||||
"rust-analyzer",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue