Compare commits

...
This repository has been archived on 2024-01-19. You can view files and clone it, but cannot push or open issues or pull requests.

24 Commits

Author SHA1 Message Date
5c8682d199 Added hash verification for assets and librairies
A bit of code (not completed) for downloading custom libraries
2023-10-07 15:09:54 +02:00
5b8effd7c4 Moved chapter to MinecraftClient struct
Creating of a new struct for fabric manifest because library resolution is different than minecraft official
2023-10-07 01:33:26 +02:00
2c84ebb87e Added modpack downloading 2023-10-06 23:32:35 +02:00
532257ffb0 Move zip download and extracting to altarik/mod.rs and implemented zip version 2023-10-04 23:57:34 +02:00
affa6d8535 Added downloading java 2023-10-04 16:48:46 +02:00
6ec89497cb Added altarik manifest to frontend, with the game version of the selected chapter given back to backend when calling download command 2023-10-03 23:03:53 +02:00
3122d324bf Moving from vuejs to react + vitejs, fix downloading bug where folder aren't created sometime 2023-10-03 21:28:46 +02:00
7fa38cf8b6 Added altarikManifest on backend 2023-10-03 10:49:50 +02:00
201bad4074 Save version index in the right folder 2023-08-23 11:27:09 +04:00
cdbf1bdcef Fix softlock, add assets download 2023-05-01 01:58:37 +02:00
7a4eb4694b Start implementing assets 2023-05-01 00:11:29 +02:00
be2c15be39 Move app.emit_all to avoid borrowing it 2023-04-26 14:17:52 +02:00
6697c38af0 Frontend now listen to event sent from backend 2023-04-25 20:08:07 +02:00
dce77e1e3d Fix seek alway giving a size equal to 0 2023-04-25 18:55:14 +02:00
4d1656f618 Doesn't return an error anymore if sha1 isn't available 2023-04-25 14:09:46 +02:00
c0a340225d File OS error 2023-04-24 14:34:29 +02:00
c0990b3fe8 readded game_profile to ClientOptions, added creation of libraries folder 2023-04-24 02:47:14 +02:00
6869f97eea Fix mutex issue 2023-04-22 23:05:11 +02:00
a2ca88b9cd Auth return Err when user close the window, temporary remove download function 2023-04-22 19:15:49 +02:00
463e66bd97 rename crate 2023-04-22 01:52:12 +02:00
e8c92cf7f0 Fix some issues 2023-04-22 01:51:05 +02:00
6cdf71b5b6 Add a dowload game button, move all related version manifest component to manifest.rs 2023-04-22 00:32:27 +02:00
4d2ed01cc0 Add GameProfile struct as aith result, serde now use stringly typed structures to parse version manifest json, add specific version json parsing 2023-04-20 19:51:36 +02:00
5ecbf08c0a Add parsing of minecraft version manifest 2023-04-20 01:09:50 +02:00
27 changed files with 3291 additions and 1122 deletions

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "Launcher-tauri",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@tauri-apps/cli": "^1.4.0",
"@types/node": "^18.7.10",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@vitejs/plugin-react": "^3.0.0",
"@welldone-software/why-did-you-render": "^7.0.1",
"typescript": "^4.9.5",
"vite": "^4.2.1"
}
}

2271
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
[package] [package]
name = "Launcher-tauri" name = "launcher-tauri"
version = "0.0.0" version = "0.0.0"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
license = "" license = ""
repository = "" repository = ""
edition = "2021" edition = "2021"
rust-version = "1.57" rust-version = "1.64"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -16,20 +16,26 @@ tauri-build = {version = "1.2", features = [] }
[dependencies] [dependencies]
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = {version = "1.2", features = ["api-all"] } tauri = {version = "1.2", features = [ "shell-open"] }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
uuid = "1.2.2" uuid = "1.2.2"
log4rs = "1.2.0" log4rs = "1.2.0"
reqwest = { version = "0.11.13", default-features = true, features = ["json"] } reqwest = { version = "0.11.13", default-features = true, features = ["json", "blocking"] }
urlencoding = "2.1.2" urlencoding = "2.1.2"
warp = "0.3.3" warp = "0.3.3"
anyhow = "1.0.66" anyhow = "1.0.66"
rand = "0.8.5" rand = "0.8.5"
directories = "5.0.0"
sha256 = "1.4.0"
tokio-tar = "0.3.1"
async_zip = { version = "0.0.15", features = ["full"] }
tokio-stream = "0.1.14"
sha1 = "0.10.6"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = [ "custom-protocol" ] default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem # this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this # DO NOT remove this
custom-protocol = [ "tauri/custom-protocol" ] custom-protocol = ["tauri/custom-protocol"]

View File

@ -3,6 +3,7 @@ use std::{fmt, net::TcpListener, sync::Arc};
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use reqwest::{header::{CONTENT_TYPE, CONNECTION, ACCEPT, AUTHORIZATION}, Client}; use reqwest::{header::{CONTENT_TYPE, CONNECTION, ACCEPT, AUTHORIZATION}, Client};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tauri::{WindowEvent, async_runtime::spawn_blocking};
use tokio::{sync::mpsc, join}; use tokio::{sync::mpsc, join};
use urlencoding::encode; use urlencoding::encode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -33,6 +34,7 @@ pub struct OauthToken {
prompt: Arc<Prompt> prompt: Arc<Prompt>
} }
#[allow(dead_code)]
struct AccessRefreshToken { struct AccessRefreshToken {
access_token: String, access_token: String,
refresh_token: String refresh_token: String
@ -50,6 +52,14 @@ pub struct ReceivedCode {
pub state: String, pub state: String,
} }
#[derive(Deserialize, Debug, Clone)]
pub struct GameProfile {
pub id: String,
pub name: String,
pub skins: Vec<Value>,
pub capes: Vec<Value>,
}
pub struct Authentification; pub struct Authentification;
impl Authentification { impl Authentification {
@ -66,6 +76,11 @@ impl Authentification {
format!("https://login.live.com/oauth20_authorize.srf?client_id={}&response_type=code&redirect_uri={}&scope=Xboxlive.signin+Xboxlive.offline_access&prompt={}&state={}", token.client_id, encode(token.redirect.as_str()), token.prompt, state) format!("https://login.live.com/oauth20_authorize.srf?client_id={}&response_type=code&redirect_uri={}&scope=Xboxlive.signin+Xboxlive.offline_access&prompt={}&state={}", token.client_id, encode(token.redirect.as_str()), token.prompt, state)
} }
#[allow(unused_must_use)]
fn send_blocking(sender: tokio::sync::mpsc::Sender<bool>) {
sender.blocking_send(true);
}
async fn fetch_oauth2_token(prompt: Prompt, app: tauri::AppHandle) -> Result<(ReceivedCode, OauthToken)> { async fn fetch_oauth2_token(prompt: Prompt, app: tauri::AppHandle) -> Result<(ReceivedCode, OauthToken)> {
let state: String = thread_rng() let state: String = thread_rng()
.sample_iter(&rand::distributions::Alphanumeric) .sample_iter(&rand::distributions::Alphanumeric)
@ -93,14 +108,37 @@ impl Authentification {
"externam", "externam",
tauri::WindowUrl::External(link.parse().unwrap()) tauri::WindowUrl::External(link.parse().unwrap())
).build().expect("Failed to build window"); ).build().expect("Failed to build window");
let received = Self::listen(port_holder.unwrap()).await?; let (sender, mut receiver) = mpsc::channel::<bool>(2);
second_window.close()?; second_window.on_window_event(move|e| {
match e {
if received.state != state { WindowEvent::Destroyed => {
bail!("CSRF check fail") let sender = sender.clone();
spawn_blocking(|| Self::send_blocking(sender));
},
_ => {}
}
});
let listener = Self::listen(port_holder.unwrap());
tokio::select! {
received = listener => {
match received {
Ok(received) => {
second_window.close()?;
if received.state != state {
bail!("CSRF check fail")
}
Ok((received, token_data))
},
Err(err) => {
bail!(err)
}
}
},
_ = receiver.recv() => {
bail!("You closed the window before completion")
}
} }
Ok((received, token_data))
} }
// fn create_link_from_prompt(prompt: Prompt) -> String { // fn create_link_from_prompt(prompt: Prompt) -> String {
@ -233,7 +271,7 @@ impl Authentification {
} }
} }
async fn fetch_minecraft_profile(mc_token: &String, reqwest_client: &Client) -> Result<(String, String)> { async fn fetch_minecraft_profile(mc_token: &String, reqwest_client: &Client) -> Result<GameProfile> {
let received: Value = reqwest_client let received: Value = reqwest_client
.get("https://api.minecraftservices.com/minecraft/profile") .get("https://api.minecraftservices.com/minecraft/profile")
.header(AUTHORIZATION, format!("Bearer {}", mc_token)) .header(AUTHORIZATION, format!("Bearer {}", mc_token))
@ -245,11 +283,15 @@ impl Authentification {
if let Some(val) = received.get("error") { if let Some(val) = received.get("error") {
bail!(String::from(val.as_str().unwrap())) bail!(String::from(val.as_str().unwrap()))
} else { } else {
Ok((String::from(received["id"].as_str().unwrap()), String::from(received["name"].as_str().unwrap()))) let received: GameProfile = match serde_json::from_value(received) {
Ok(gp) => gp,
Err(err) => bail!(err),
};
Ok(received)
} }
} }
pub async fn login(prompt: Prompt, app: tauri::AppHandle) -> Result<(String, String)> { pub async fn login(prompt: Prompt, app: tauri::AppHandle) -> Result<GameProfile> {
let reqwest_client = Client::new(); let reqwest_client = Client::new();
let oauth_token = Self::fetch_oauth2_token(prompt, app).await?; let oauth_token = Self::fetch_oauth2_token(prompt, app).await?;
let access_refresh_token = Self::fetch_token(oauth_token.0, oauth_token.1, &reqwest_client).await?; let access_refresh_token = Self::fetch_token(oauth_token.0, oauth_token.1, &reqwest_client).await?;

View File

@ -0,0 +1,28 @@
//! module for fabric version detail manifest
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct CustomVersionManifest {
#[serde(rename(serialize = "inheritsFrom", deserialize = "inheritsFrom"))]
pub inherits_from: String,
#[serde(rename(serialize = "mainClass", deserialize = "mainClass"))]
pub main_class: String,
pub libraries: Vec<CustomLibrary>,
pub arguments: CustomArguments,
pub id: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
pub t_type: String,
}
#[derive(Serialize, Deserialize)]
pub struct CustomLibrary {
pub name: String,
pub url: String,
}
#[derive(Serialize, Deserialize)]
pub struct CustomArguments {
jvm: Vec<String>,
game: Vec<String>,
}

View File

@ -0,0 +1,277 @@
pub mod custom_version_manifest;
use std::path::{PathBuf, Path};
use anyhow::{Result, bail, anyhow};
use reqwest::Client;
use serde::{Serialize, Deserialize};
use async_zip::base::read::seek::ZipFileReader;
use sha1::{Digest, Sha1};
use tokio::{fs::{OpenOptions, File, self}, sync::mpsc, io::{AsyncWriteExt, AsyncReadExt}};
use tokio_tar::Archive;
use tokio_stream::StreamExt;
use crate::launcher::ProgressMessage;
use self::custom_version_manifest::CustomVersionManifest;
use super::{ACTUAL_OS, manifest::{OSName, VersionDetail}};
#[derive(Serialize, Deserialize, Clone)]
pub struct AltarikManifest {
pub chapters: Vec<Chapter>
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Chapter {
pub title: String,
pub description: String,
#[serde(rename(serialize = "minecraftVersion", deserialize = "minecraftVersion"))]
pub minecraft_version: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
pub mc_type: String,
#[serde(rename(serialize = "customVersion", deserialize = "customVersion"))]
pub custom_version: String,
pub modspack: ModsPack,
pub java: Java,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ModsPack {
pub mods: Vec<String>,
pub sha1sum: Vec<String>
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Java {
pub platform: JavaPlatform,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct JavaPlatform {
pub win32: Option<JavaPlatformArch>,
pub linux: Option<JavaPlatformArch>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct JavaPlatformArch {
pub x64: JavaDetails
}
#[derive(Serialize, Deserialize, Clone)]
pub struct JavaDetails {
pub name: String,
pub link: String,
pub sha256sum: String,
}
impl AltarikManifest {
pub async fn get_altarik_manifest(reqwest: &Client) -> Result<AltarikManifest> {
let received: AltarikManifest = reqwest
.get("https://launcher.altarik.fr")
.send()
.await?
.json()
.await?;
Ok(received)
}
}
impl Chapter {
pub async fn get_custom_version_detail_manifest(&self, version_dir: &PathBuf) -> Result<CustomVersionManifest> {
let filepath = version_dir.join(self.custom_version.to_string()).join(format!("{}.json", self.custom_version.to_string()));
let mut file = File::open(filepath).await?;
let mut content = String::new();
file.read_to_string(&mut content).await?;
Ok(serde_json::from_str(&content)?)
}
}
impl JavaPlatform {
pub async fn download_java(&self, root_path: &Path, reqwest: &Client, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> {
let download_path = root_path.join("runtime").join("download");
let (url, extension) = match ACTUAL_OS {
OSName::Linux => (self.linux.clone(), "tar.gz"),
OSName::Windows => (self.win32.clone(), "zip"),
_ => bail!("Your current is not supported")
};
let url = match url {
Some(url) => url,
None => bail!("No available executable available for your platform")
};
let filepath = download_path.join(format!("{}.{}", url.x64.name.clone(), extension));
let mut should_download = false;
if !filepath.exists() {
should_download = true;
} else {
let hash = sha256::try_digest(filepath.clone());
match hash {
Ok(hash) => {
if hash != url.x64.sha256sum {
println!("Hash of java archive is not correct, redownloading");
should_download = true;
}
},
Err(_) => should_download = true
}
}
if should_download {
println!("Downloading java");
if filepath.exists() {
fs::remove_file(filepath.clone()).await?; // remove content before writing and appending to it
}
let mut file = OpenOptions::new()
.write(true)
.append(true)
.create(true)
.open(filepath)
.await?;
let mut res = reqwest.get(url.x64.link.clone()).send().await?;
log_channel.send(ProgressMessage { p_type: String::from("java"), current: 0, total: 100 }).await?;
let length = if let Some(length) = res.content_length() {
length as usize
} else {
0
};
let mut downloaded_size = 0;
while let Some(chunk) = res.chunk().await? {
downloaded_size += chunk.len();
file.write_all(&chunk).await?;
log_channel.send(ProgressMessage { p_type: String::from("java"), current: if length != 0 { downloaded_size / length } else { 0 }, total: 100 }).await?;
}
println!("{} downloaded", url.x64.name)
} else {
println!("{} already downloaded", url.x64.name)
}
Ok(())
}
pub async fn extract_java(&self, root_path: &Path) -> Result<()> {
let (url, extension) = match ACTUAL_OS {
OSName::Linux => (self.linux.clone(), "tar.gz"),
OSName::Windows => (self.win32.clone(), "zip"),
_ => bail!("Your current is not supported")
};
let url = match url {
Some(url) => url,
None => bail!("No available executable available for your platform")
};
let archive_path = root_path.join("runtime").join("download").join(format!("{}.{}", url.x64.name, extension));
let extract_path = root_path.join("runtime");
extract_zip(&archive_path, &extract_path).await?;
Ok(())
}
}
impl ModsPack {
pub async fn download_mods(&self, reqwest: &Client, modpack_dir: &PathBuf, root_dir: &PathBuf, log_channel: mpsc::Sender<ProgressMessage>) -> Result<()> {
for index in 0..self.mods.len() {
log_channel.send(ProgressMessage { p_type: "mods".to_string(), current: index, total: self.mods.len() }).await?;
let mod_url = self.mods.get(index).ok_or(anyhow!("Cannot get mod download link"))?;
let sha1 = self.sha1sum.get(index).ok_or(anyhow!("Cannot verify mod integrity"))?;
let filepath = modpack_dir.join(format!("modpack{}.zip", index));
let should_download = self.should_download_mod(&sha1, &filepath).await?;
if should_download {
println!("Need to download mod {}", mod_url);
let mut res = reqwest.get(mod_url)
.send()
.await?;
let mut file = fs::OpenOptions::new()
.write(true)
.create_new(true)
.append(true)
.open(filepath.clone())
.await?;
while let Some(chunk) = res.chunk().await? {
file.write_all(&chunk).await?;
}
} else {
println!("Mod {} already downloaded", sha1);
}
extract_zip(&filepath, &root_dir).await?;
}
Ok(())
}
async fn should_download_mod(&self, mod_sha1: &&String, filepath: &PathBuf) -> Result<bool> {
if filepath.exists() {
let mut hasher = Sha1::new();
let mut file = File::open(filepath).await?;
let mut content = Vec::new();
file.read_to_end(&mut content).await?;
hasher.update(content);
let hash = hasher.finalize();
// let b16 = base16ct::upper::encode_string(hash);
if format!("{:x}", hash.clone()) != mod_sha1.to_lowercase() {
println!("Correct: {:?}, current: {:X}", mod_sha1, hash);
fs::remove_file(filepath).await?;
Ok(true)
} else {
Ok(false)
}
} else {
Ok(true)
}
}
}
async fn extract_targz(archive_path: PathBuf, extract_path: PathBuf) -> Result<()> {
let file = File::open(archive_path.clone()).await?;
let mut archive = Archive::new(file);
let mut entries = archive.entries()?;
while let Some(entry) = entries.next().await {
let file = entry?;
println!("{:?}", archive_path);
println!("{}", file.path()?.to_string_lossy().to_string());
// TODO when I'll be on a linux
}
Ok(())
}
async fn extract_zip(archive_path: &PathBuf, extract_path: &PathBuf) -> Result<()> {
// TODO add log_channel to send progression to user
let file = File::open(archive_path.clone()).await?;
let mut reader = ZipFileReader::with_tokio(file).await?;
for index in 0..reader.file().entries().len() {
if let Some(entry) = reader.file().entries().get(index) {
let entry = entry.entry();
let filename = entry.filename().as_str()?;
let path = extract_path.join(filename);
if entry.dir()? {
if path.exists() {
fs::remove_dir_all(path.clone()).await?; // clear folder before continue, avoid injection
}
fs::create_dir_all(path).await?; // recreating the folder then
} else {
let mut entry_reader = reader.reader_with_entry(index).await?;
if path.exists() {
fs::remove_file(&path).await?;
}
let mut writer = OpenOptions::new()
.write(true)
.append(true)
.create_new(true)
.open(&path)
.await?;
let mut buf = Vec::with_capacity(1024 * 128);
entry_reader.read_to_end_checked(&mut buf).await?;
writer.write_all(&buf).await?;
}
}
}
Ok(())
}

View File

@ -0,0 +1,147 @@
use std::collections::HashMap;
use anyhow::{Result, bail};
use reqwest::Client;
use serde::{Serialize, Deserialize};
use serde_json::{Value, Map};
use super::VersionType;
#[derive(Serialize, Deserialize, Debug)]
pub struct VersionManifestV2 {
latest: Value,
versions: Vec<Version>
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Version {
id: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
v_type: VersionType,
url: String,
sha1: String
}
pub async fn get_version_manifest(reqwest: &Client) -> Result<VersionManifestV2> {
let received: VersionManifestV2 = reqwest
.get("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")
.send()
.await?
.json()
.await?;
Ok(received)
}
pub async fn get_version_from_manifest<'a>(manifest: &'a VersionManifestV2, game_version: String, version_type: &VersionType) -> Result<&'a Version> {
for i in manifest.versions.iter().enumerate() {
let id = i.1.id.clone();
let v_type = i.1.v_type;
if id == game_version && &v_type == version_type {
return Ok(i.1);
}
}
bail!("Version not Found")
}
#[derive(Serialize, Deserialize)]
pub struct VersionDetail {
arguments: Map<String, Value>,
#[serde(rename(serialize = "assetIndex", deserialize = "assetIndex"))]
pub asset_index: AssetIndex,
pub assets: String,
#[serde(rename(serialize = "complianceLevel", deserialize = "complianceLevel"))]
compliance_level: i32,
downloads: Map<String, Value>,
id: String,
#[serde(rename(serialize = "javaVersion", deserialize = "javaVersion"))]
java_version: Map<String, Value>,
pub libraries: Vec<Library>,
logging: Map<String, Value>,
#[serde(rename(serialize = "mainClass", deserialize = "mainClass"))]
main_class: String,
#[serde(rename(serialize = "type", deserialize = "type"))]
v_type: VersionType
}
#[derive(Serialize, Deserialize)]
pub struct AssetIndex {
pub id: String,
pub sha1: String,
pub size: usize,
#[serde(rename(serialize = "totalSize", deserialize = "totalSize"))]
pub total_size: usize,
pub url: String
}
#[derive(Serialize, Deserialize)]
pub struct Library {
pub downloads: LibraryDownload,
pub name: String,
pub rules: Option<Vec<LibraryRule>>
}
#[derive(Serialize, Deserialize)]
pub struct LibraryDownload {
pub artifact: LibraryArtifact
}
#[derive(Serialize, Deserialize)]
pub struct LibraryRule {
pub action: String,
pub os: LibraryOSRule
}
#[derive(Serialize, Deserialize)]
pub struct LibraryOSRule {
pub name: OSName,
}
#[derive(Serialize, Deserialize, PartialEq)]
pub enum OSName {
#[serde(rename(serialize = "osx", deserialize = "osx"))]
MacOsX,
#[serde(rename(serialize = "linux", deserialize = "linux"))]
Linux,
#[serde(rename(serialize = "windows", deserialize = "windows"))]
Windows
}
#[derive(Serialize, Deserialize)]
pub struct LibraryArtifact {
pub path: String,
pub sha1: String,
pub size: u64,
pub url: String,
}
pub async fn get_version_detail(reqwest: &Client, version : &Version) -> Result<VersionDetail> {
let received: VersionDetail = reqwest
.get(version.url.clone())
.send()
.await?
.json()
.await?;
Ok(received)
}
#[derive(Serialize, Deserialize)]
pub struct AssetsManifest {
pub objects: HashMap<String, AssetObject>,
}
#[derive(Serialize, Deserialize)]
pub struct AssetObject {
pub hash: String,
pub size: u64,
}
pub async fn get_version_assets(reqwest: &Client , assets_index: &AssetIndex) -> Result<AssetsManifest> {
let received: AssetsManifest = reqwest
.get(assets_index.url.clone())
.send()
.await?
.json()
.await?;
Ok(received)
}

View File

@ -0,0 +1,334 @@
mod manifest;
pub mod altarik;
use std::path::{Path, self, PathBuf};
use anyhow::{Result, bail};
use reqwest::{Client, StatusCode};
use serde::{Serialize, Deserialize};
use sha1::{Digest, Sha1};
use tokio::{fs::{self, File}, io::{AsyncWriteExt, AsyncSeekExt, AsyncReadExt}, sync::mpsc};
use crate::authentification::GameProfile;
use self::{manifest::{VersionDetail, get_version_manifest, get_version_from_manifest, get_version_detail, Library, OSName, get_version_assets, AssetsManifest}, altarik::{Chapter, custom_version_manifest::CustomVersionManifest}};
#[cfg(target_os="windows")]
const ACTUAL_OS: OSName = OSName::Windows;
#[cfg(target_os="linux")]
const ACTUAL_OS: OSName = OSName::Linux;
#[cfg(target_os="macos")]
const ACTUAL_OS: OSName = OSName::MacOsX;
#[cfg(not(any(target_arch="x86_64", target_arch="x86")))]
compile_error!("Your architecture is not supported");
#[derive(Clone, serde::Serialize, Debug)]
pub struct ProgressMessage {
p_type: String,
current: usize,
total: usize,
}
pub struct ClientOptions<'a> {
pub authorization: GameProfile,
pub log_channel: mpsc::Sender<ProgressMessage>,
pub root_path: &'a Path,
pub java_path: &'a Path,
/// deprecated, will be remove
pub version_number: String,
pub version_type: VersionType,
// version_custom: String, // for a next update
pub memory_min: String,
pub memory_max: String,
}
pub struct MinecraftClient<'a> {
opts: &'a ClientOptions<'a>,
details: VersionDetail,
custom_details: Option<CustomVersionManifest>,
assets: AssetsManifest,
reqwest_client: Client,
chapter: Chapter,
}
impl<'a> MinecraftClient<'_> {
pub async fn new(opts: &'a ClientOptions<'a>, chapter: Chapter) -> Result<MinecraftClient<'a>> {
let reqwest_client = Client::new();
let manifest = Self::load_manifest(&reqwest_client, &opts).await?;
let details = manifest.0;
let assets = manifest.1;
Ok(MinecraftClient {
opts,
reqwest_client,
details,
custom_details: None,
assets,
chapter
})
}
async fn load_manifest(reqwest_client: &Client, opts: &ClientOptions<'a>) -> Result<(VersionDetail, AssetsManifest)> {
let manifest = get_version_manifest(&reqwest_client).await?;
let version = get_version_from_manifest(&manifest, opts.version_number.clone(), &opts.version_type).await?;
let details = get_version_detail(&reqwest_client, version).await?;
let version_assets = get_version_assets(reqwest_client, &details.asset_index).await?;
Ok((details, version_assets))
}
async fn create_dirs(&self) -> Result<()> {
let folders = vec![
self.opts.root_path.join("libraries"),
self.opts.root_path.join("assets").join("objects"),
self.opts.root_path.join("assets").join("indexes"),
self.opts.root_path.join("runtime").join("download"),
self.opts.root_path.join("mods")
];
let mut tasks = Vec::with_capacity(folders.len());
for folder in folders {
if !folder.exists() {
tasks.push(tokio::spawn(async { fs::create_dir_all(folder).await }));
}
}
for task in tasks {
let _ = task.await?;
}
Ok(())
}
async fn save_version_index(&self) -> Result<()> {
let indexes = &self.opts.root_path.join("assets").join("indexes");
let mut filename = self.details.assets.clone();
filename.push_str(".json");
let filepath = indexes.join(filename);
let file = File::create(filepath).await;
match file {
Ok(mut f) => {
f.write_all(serde_json::to_string(&self.details)?.as_bytes()).await?;
Ok(())
},
Err(err) => bail!(err),
}
}
pub async fn download_requirements(&mut self) -> Result<()> {
// create root folder if it doesn't exist
self.create_dirs().await?;
let lib = &self.opts.root_path.join("libraries");
let asset = &self.opts.root_path.join("assets").join("objects");
let modpack = &self.opts.root_path.join("modpack").join(self.chapter.title.clone());
if !modpack.exists() {
fs::create_dir_all(modpack).await?;
}
self.clear_folder().await?;
self.save_version_index().await?;
self.chapter.java.platform.download_java(self.opts.root_path, &self.reqwest_client, self.opts.log_channel.clone()).await?;
self.chapter.java.platform.extract_java(self.opts.root_path).await?;
self.chapter.modspack.download_mods(&self.reqwest_client, modpack, &self.opts.root_path.to_path_buf(), self.opts.log_channel.clone()).await?;
self.custom_details = Some(self.chapter.get_custom_version_detail_manifest(&self.opts.root_path.join("versions")).await?);
self.download_libraries(lib).await?;
self.download_custom_libraries(lib).await?;
self.download_assets(asset).await?;
self.download_jar().await?;
self.opts.log_channel.send(ProgressMessage { p_type: "completed".to_string(), current: 0, total: 0 }).await?;
Ok(())
}
async fn download_libraries(&mut self, lib: &PathBuf) -> Result<()> {
self.filter_non_necessary_librairies();
let total = self.details.libraries.len();
for (progress, i) in self.details.libraries.iter().enumerate() {
let p = i.downloads.artifact.path.clone();
let mut splited = p.split("/").collect::<Vec<&str>>();
let filename = splited.pop().ok_or(anyhow::anyhow!("Invalid filename"))?; // remove last element
let p = splited.join(path::MAIN_SEPARATOR_STR);
let p = &lib.join(p);
fs::create_dir_all(p).await?;
let file_path = p.join(filename);
let mut file = Self::select_file_option(&file_path, i.downloads.artifact.size).await?;
let size = file.seek(std::io::SeekFrom::End(0)).await?;
file.seek(std::io::SeekFrom::Start(0)).await?;
let mut hasher = Sha1::new();
let mut content = Vec::new();
file.read_to_end(&mut content).await?;
hasher.update(content);
let hash = hasher.finalize();
if size != i.downloads.artifact.size || format!("{:x}", hash) != i.downloads.artifact.sha1.to_lowercase() {
let url = i.downloads.artifact.url.clone();
let mut sha_url = url.clone();
sha_url.push_str(".sha1");
let sha1 = self.reqwest_client
.get(sha_url)
.send()
.await?;
if sha1.status() == StatusCode::OK {
let sha1 = sha1.text().await?;
if sha1 != i.downloads.artifact.sha1 {
bail!("Sha1 {:?} of {} is invalid, a malicious file might be present on the remote server, should be {}", sha1, i.name, i.downloads.artifact.sha1)
}
}
let content = self.reqwest_client
.get(url)
.send()
.await?
.bytes()
.await?;
file.write_all(&content).await?;
println!("{} downloaded", i.name);
} else {
println!("{} already downloaded", i.name);
}
self.opts.log_channel.send( ProgressMessage { p_type: "libraries".to_string(), current: progress + 1, total }).await?;
}
Ok(())
}
async fn download_custom_libraries(&self, lib_dir: &PathBuf) -> Result<()> {
if let Some(custom) = &self.custom_details {
for i in &custom.libraries {
let splited: Vec<&str> = i.name.split(":").collect();
if splited.len() == 3 {
let file_name = format!("{}-{}.jar", splited[1], splited[2]);
let sha256_url = format!("{}.sha256", file_name);
let url = format!("{}/{}/{}/{}/{}", i.url, splited[0].replace(".", "/"), splited[1], splited[2], file_name);
let sha256 = self.reqwest_client
.get(sha256_url)
.send()
.await?
.text()
.await?;
let sep = path::MAIN_SEPARATOR_STR;
let p = format!("{}{sep}{}{sep}{}{sep}{}", splited[0].replace(".", sep), splited[1], splited[2], splited[1]);
let file_path = lib_dir.join(p);
if !file_path.exists() {
}
} else {
bail!("Cannot resolve dependency url");
}
}
}
Ok(())
}
async fn download_jar(&mut self) -> Result<()> {
Ok(())
}
/// delete and recreate some folder, in particular mods and version folder
async fn clear_folder(&self) -> Result<()> {
for i in [self.opts.root_path.join("mods"), self.opts.root_path.join("versions")] {
fs::remove_dir_all(&i).await?;
fs::create_dir_all(&i).await?;
}
Ok(())
}
async fn select_file_option(file_path: &PathBuf, expected_size: u64) -> Result<File> {
if (&file_path).exists() {
let f = fs::File::open(&file_path).await;
match f {
Ok(mut f) => { if f.seek(std::io::SeekFrom::End(0)).await? == expected_size { Ok(f) } else { Ok(fs::File::create(file_path).await?) } },
Err(err) => bail!(err),
}
} else {
Ok(fs::File::create(file_path).await?)
}
}
/// Filter non necessary librairies for the current OS
fn filter_non_necessary_librairies(&mut self) {
self.details.libraries.retain(|e| { Self::should_use_library(e) });
}
async fn download_assets(&mut self, object_folder: &PathBuf) -> Result<()> {
let total: usize = self.assets.objects.len();
for (progress, (_, value)) in self.assets.objects.iter().enumerate() {
let hash = value.hash.clone();
let two_hex = hash.chars().take(2).collect::<String>();
let hex_folder = object_folder.join(&two_hex);
if !hex_folder.exists() {
fs::create_dir(&hex_folder).await?;
}
let file_path = hex_folder.join(&hash);
let mut file = Self::select_file_option(&file_path, value.size).await?;
let size = file.seek(std::io::SeekFrom::End(0)).await?;
file.seek(std::io::SeekFrom::Start(0)).await?;
let mut hasher = Sha1::new();
let mut content = Vec::new();
file.read_to_end(&mut content).await?;
hasher.update(content);
let hash_file = hasher.finalize();
if size != value.size || format!("{:x}", hash_file) != value.hash {
let url = format!("https://resources.download.minecraft.net/{}/{}", two_hex, hash);
let received = self.reqwest_client
.get(url)
.send()
.await?
.bytes()
.await?;
file.write_all(&received).await?;
println!("{} downloaded", value.hash);
} else {
println!("{} already downloaded", value.hash);
}
self.opts.log_channel.send( ProgressMessage { p_type: "assets".to_string(), current: progress + 1, total }).await?;
}
Ok(())
}
pub async fn launch_minecraft(&mut self) -> Result<()> {
Ok(())
}
fn should_use_library(library: &Library) -> bool {
match &library.rules {
Some(rules) => {
for i in rules.iter().enumerate() {
let op = if i.1.action == "allow" {
true
} else {
false
};
if i.1.os.name == ACTUAL_OS {
return op;
}
}
false
},
None => {
true
}
}
}
pub fn launch_game(&self) {
// TODO
}
}
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)]
pub enum VersionType {
#[serde(alias = "release")]
Release,
#[serde(alias = "snapshot")]
Snapshot,
#[serde(alias = "old_alpha")]
OldAlpha,
#[serde(alias = "old_beta")]
OldBeta,
}

View File

@ -4,29 +4,149 @@
)] )]
pub mod authentification; pub mod authentification;
pub mod launcher;
use authentification::{Authentification, Prompt}; use std::sync::Mutex;
use authentification::{Authentification, Prompt, GameProfile};
use anyhow::Result; use anyhow::Result;
use directories::BaseDirs;
use launcher::{MinecraftClient, ClientOptions, ProgressMessage, altarik::{AltarikManifest, Chapter}};
use reqwest::Client;
use tauri::Manager;
use tokio::sync::mpsc;
struct CustomState (Option<GameProfile>, Option<AltarikManifest>);
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command] #[tauri::command]
fn greet(name: &str) -> String { async fn login(app: tauri::AppHandle, _window: tauri::Window, state: tauri::State<'_, Mutex<CustomState>>) -> Result<String, String> {
format!("Hello, {}! You've been greeted from Rust!", name) let result = Authentification::login(Prompt::SelectAccount, app).await;
match result {
Ok(val) => {
let name = val.name.clone();
match state.lock() {
Ok(mut game_profile) => {
game_profile.0 = Some(val);
Ok(format!("Hello {}", name))
},
Err(err) => {
Err(err.to_string())
}
}
},
Err(err) => Err(err.to_string())
}
} }
#[tauri::command] #[tauri::command]
async fn login(app: tauri::AppHandle, _window: tauri::Window) -> Result<String, String> { async fn load_altarik_manifest(state: tauri::State<'_, Mutex<CustomState>>) -> Result<AltarikManifest, String> {
let result = Authentification::login(Prompt::SelectAccount, app).await; let reqwest_client = Client::new();
match result { let altarik_manifest = AltarikManifest::get_altarik_manifest(&reqwest_client).await;
Ok(val) => Ok(format!("Hello {}", val.1)), match altarik_manifest {
Err(err) => Err(err.to_string()) Ok(val) => {
match state.lock() {
Ok(mut global_manifest) => {
let _ = global_manifest.1.insert(val.clone());
Ok(val)
},
Err(err) => {
Err(err.to_string())
}
}
},
Err(err) => {
Err(err.to_string())
}
}
}
#[tauri::command]
async fn download(selected_chapter: usize, app: tauri::AppHandle, state: tauri::State<'_, Mutex<CustomState>>) -> Result<String, String> {
let chapter = match state.lock() {
Ok(lock) => {
match &lock.1 {
Some(manifest) => {
match manifest.chapters.get(selected_chapter) {
Some(val) => {
val.clone()
},
None => return Err("Selected chapter doesn't exist".to_string())
}
},
None => return Err("Cannot load altarik manifest".to_string())
}
},
Err(err) => return Err(err.to_string())
};
if let Some(base_dir) = BaseDirs::new() {
let data_folder = base_dir.data_dir().join(".altarik_test");
let root_path = data_folder.as_path();
let java_path = root_path.join("java");
let game_profile = match state.lock() {
Ok(res) => Ok(res.0.clone()),
Err(err) => Err(err.to_string())
}?;
if game_profile.is_none() {
return Err("You're not connected".to_string());
}
let (sender, receiver) = mpsc::channel(60);
let opts = ClientOptions {
authorization: game_profile.unwrap(),
log_channel: sender.clone(),
root_path,
java_path: &java_path.as_path(),
version_number: chapter.minecraft_version.clone(),
version_type: launcher::VersionType::Release,
memory_min: "2G".to_string(),
memory_max: "4G".to_string(),
};
drop(sender);
let res = tokio::join!(
download_libraries(opts, chapter),
read_channel(receiver, app),
);
res.0
} else {
Err("Cannot download files".to_string())
}
}
async fn download_libraries(opts: ClientOptions<'_>, chapter: Chapter) -> Result<String, String> {
let client = MinecraftClient::new(&opts, chapter).await;
let res = match client {
Ok(mut client) => {
match client.download_requirements().await {
Ok(_) => {
Ok("Content downloaded".to_string())
},
Err(err) => {
Err(err.to_string())
}
}
},
Err(err) => {
Err(err.to_string())
}
};
res
}
async fn read_channel(mut receiver: mpsc::Receiver<ProgressMessage>, app: tauri::AppHandle) -> Result<()> {
loop {
match receiver.recv().await {
Some(msg) => { app.emit_all("progress", msg)? },
None => break Ok(())
}
} }
} }
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
tauri::Builder::default() tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet, login]) .manage(Mutex::new(CustomState(None, None)))
.invoke_handler(tauri::generate_handler![login, download, load_altarik_manifest])
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");
} }

View File

@ -1,10 +1,10 @@
{ {
"build": { "build": {
"beforeDevCommand": "", "beforeDevCommand": "yarn dev",
"beforeBuildCommand": "", "beforeBuildCommand": "yarn build",
"devPath": "../src", "devPath": "http://localhost:1420",
"distDir": "../src", "distDir": "../dist",
"withGlobalTauri": true "withGlobalTauri": false
}, },
"package": { "package": {
"productName": "Launcher-tauri", "productName": "Launcher-tauri",
@ -12,16 +12,14 @@
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {
"all": true "all": false,
"shell": {
"all": false,
"open": true
}
}, },
"bundle": { "bundle": {
"active": true, "active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [ "icon": [
"icons/32x32.png", "icons/32x32.png",
"icons/128x128.png", "icons/128x128.png",
@ -29,30 +27,12 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"identifier": "com.tauri.dev", "identifier": "fr.altarik.launcher",
"longDescription": "", "targets": "all"
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
}, },
"security": { "security": {
"csp": null "csp": null
}, },
"updater": {
"active": false
},
"windows": [ "windows": [
{ {
"fullscreen": false, "fullscreen": false,

12
src/App.tsx Normal file
View File

@ -0,0 +1,12 @@
import { invoke, event } from "@tauri-apps/api";
import LoginPage from './components/LoginPage'
export default function App() {
return (
<>
<LoginPage />
</>
)
}

View File

@ -0,0 +1,18 @@
import { AltarikManifest } from "../models/manifest/AltarikManifest";
import ChapterList from "./ChapterList";
interface Props {
manifest: AltarikManifest | undefined,
selectedChapter: number,
onClickFunction: Function
}
export default function AltarikManifestComponent({manifest, selectedChapter, onClickFunction} : Props) {
return (
<>
{manifest != undefined ? <ChapterList chapters={manifest.chapters} selectedChapter={selectedChapter} onClickFunction={onClickFunction} /> : <></>}
</>
)
}

View File

@ -0,0 +1,16 @@
import { title } from "process"
import { Chapter } from "../models/manifest/AltarikManifest"
import { MouseEventHandler } from "react"
interface Props {
chapter: Chapter,
isSelected: boolean,
onClickFunction: MouseEventHandler<HTMLButtonElement>
}
export default function ChapterItem({ chapter, isSelected, onClickFunction } : Props) {
return (
<button className={isSelected ? "selected": ""} onClick={onClickFunction}>{chapter.title} -- {chapter.minecraftVersion}</button>
)
}

View File

@ -0,0 +1,25 @@
import { AltarikManifest, Chapter } from "../models/manifest/AltarikManifest"
import ChapterItem from "./ChapterItem"
interface Props {
chapters: Chapter[],
selectedChapter: number,
onClickFunction: Function
}
export default function ChapterList({ chapters, selectedChapter, onClickFunction }: Props) {
return (
<div id="chaptersList">
{
chapters.map((chapter, key) => (
<ChapterItem chapter={chapter} key={key} isSelected={key === selectedChapter} onClickFunction={() => onClickFunction(key)}/>
))
}
</div>
)
}

View File

@ -0,0 +1,91 @@
import { useEffect, useState } from "react"
import { invoke, event } from "@tauri-apps/api";
import { AltarikManifest } from "../models/manifest/AltarikManifest";
import AltarikManifestComponent from "./AltarikManifestComponent";
interface ProgressMessage {
p_type: String,
current: number,
total: number
}
export default function LoginPage() {
const [greetMessage, setGreetMessage] = useState<String>("");
const [isLogged, setIsLogged] = useState<boolean>(false);
const [loginButtonDisabled, setLoginButtonDisabled] = useState<boolean>(false);
const [altarikManifest, setAltarikManifest] = useState<AltarikManifest>();
const [selectedChapter, setSelectChapter] = useState<number>(-1);
useEffect(() => {
event.listen('progress', (e) => {
let v = e.payload as ProgressMessage;
setGreetMessage("{type: " + v.p_type + ", current: " + v.current + ", total: " + v.total + "}");
// setGreetMessage(String(e.payload));
});
}, [])
useEffect(() => {
if(isLogged) {
invoke('load_altarik_manifest', {}).then(val => {
setAltarikManifest(val as AltarikManifest)
}).catch(err => {
setGreetMessage("Cannot load altarik manifest: " + err)
})
} else {
setAltarikManifest(undefined)
}
}, [isLogged])
async function login () {
if(!isLogged && !loginButtonDisabled) {
setLoginButtonDisabled(true);
invoke("login", {}).then(value => {
setGreetMessage(String(value));
setIsLogged(true);
}).catch(err => {
setGreetMessage("Error: " + err)
setLoginButtonDisabled(false);
})
}
}
async function download() {
if(isLogged) {
if(selectedChapter !== -1 && altarikManifest !== undefined) {
invoke("download", { selectedChapter: selectedChapter }).then(value => {
setGreetMessage(String(value))
}).catch(err => {
console.log("An error occured")
setGreetMessage("Error: " + err)
})
} else {
setGreetMessage("Please select a chapter first")
}
}
}
async function select_chapter(key: number) {
setSelectChapter(key)
}
return (
<>
<h1>Welcome to Tauri!</h1>
<div className="row">
<div>
<button id="greet-button" type="button" onClick={login}>Login to minecraft</button>
<button id="download-button" className={!isLogged ? "hide" : ""} onClick={download} v-on:click="download">Download game</button>
</div>
</div>
<p id="greet-msg">{ greetMessage }</p>
<hr />
<AltarikManifestComponent manifest={altarikManifest} selectedChapter={selectedChapter} onClickFunction={select_chapter} />
</>
)
}

View File

@ -1,32 +0,0 @@
export default {
data() {
return {
button_message: "Login to minecraft",
greet_message: ""
}
},
methods: {
login (e) {
e.preventDefault()
this.invoke("login", {}).then(value => {
this.greet_message = value
}).catch(err => {
this.greet_message = "Error: " + err
})
}
},
props: {
invoke: Object
},
template: `
<h1>Welcome to Tauri!</h1>
<div class="row">
<div>
<button id="greet-button" type="button" v-on:click="login">{{ button_message }}</button>
</div>
</div>
<p id="greet-msg">{{ greet_message }}</p>
`
}

View File

@ -16,7 +16,7 @@
<body> <body>
<div id="container"> <div id="container">
<loginpage :invoke="invoke"></loginpage> <loginpage :invoke="invoke" :listen="listen"></loginpage>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,19 +0,0 @@
const { invoke } = window.__TAURI__.tauri;
const { createApp } = Vue
import loginpage from './components/login.js'
createApp({
data() {
return {
invoke: invoke
}
},
mounted() {
},
components: {
loginpage
}
}).mount('#container')

10
src/main.tsx Normal file
View File

@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View File

@ -0,0 +1,9 @@
export interface AltarikManifest {
chapters: Chapter[]
}
export interface Chapter {
title: String,
minecraftVersion: String,
}

View File

@ -84,6 +84,14 @@ button {
margin-right: 5px; margin-right: 5px;
} }
.hide {
display: none;
}
button.selected {
color: red;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
color: #f6f6f6; color: #f6f6f6;

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

22
tsconfig.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
tsconfig.node.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

27
vite.config.ts Normal file
View File

@ -0,0 +1,27 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
// https://vitejs.dev/config/
export default defineConfig(async () => ({
plugins: [react()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
port: 1420,
strictPort: true,
},
// to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ["VITE_", "TAURI_"],
build: {
// Tauri supports es2021
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
// don't minify for debug builds
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
// produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG,
},
}));

749
yarn.lock Normal file
View File

@ -0,0 +1,749 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@ampproject/remapping@^2.2.0":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.22.9":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==
"@babel/core@^7.20.12":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83"
integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-compilation-targets" "^7.22.15"
"@babel/helper-module-transforms" "^7.23.0"
"@babel/helpers" "^7.23.0"
"@babel/parser" "^7.23.0"
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.0"
"@babel/types" "^7.23.0"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-compilation-targets@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz#0698fc44551a26cf29f18d4662d5bf545a6cfc52"
integrity sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==
dependencies:
"@babel/compat-data" "^7.22.9"
"@babel/helper-validator-option" "^7.22.15"
browserslist "^4.21.9"
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-module-imports@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0"
integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==
dependencies:
"@babel/types" "^7.22.15"
"@babel/helper-module-transforms@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz#3ec246457f6c842c0aee62a01f60739906f7047e"
integrity sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==
dependencies:
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-module-imports" "^7.22.15"
"@babel/helper-simple-access" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/helper-validator-identifier" "^7.22.20"
"@babel/helper-plugin-utils@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
"@babel/helper-simple-access@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==
"@babel/helpers@^7.23.0":
version "7.23.1"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==
dependencies:
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.0"
"@babel/types" "^7.23.0"
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-transform-react-jsx-self@^7.18.6":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e"
integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/plugin-transform-react-jsx-source@^7.19.6":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c"
integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@esbuild/android-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==
"@esbuild/android-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682"
integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==
"@esbuild/android-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2"
integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==
"@esbuild/darwin-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1"
integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==
"@esbuild/darwin-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d"
integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==
"@esbuild/freebsd-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54"
integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==
"@esbuild/freebsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e"
integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==
"@esbuild/linux-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0"
integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==
"@esbuild/linux-arm@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0"
integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==
"@esbuild/linux-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7"
integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==
"@esbuild/linux-loong64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d"
integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==
"@esbuild/linux-mips64el@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231"
integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==
"@esbuild/linux-ppc64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb"
integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==
"@esbuild/linux-riscv64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6"
integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==
"@esbuild/linux-s390x@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071"
integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==
"@esbuild/linux-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338"
integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==
"@esbuild/netbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1"
integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==
"@esbuild/openbsd-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae"
integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==
"@esbuild/sunos-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d"
integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==
"@esbuild/win32-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9"
integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==
"@esbuild/win32-ia32@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102"
integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==
"@esbuild/win32-x64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d"
integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
dependencies:
"@jridgewell/set-array" "^1.0.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.19"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@tauri-apps/api@^1.4.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.5.0.tgz#46a0f9b6edd4bfc39be32afc4ad242e0b8cca3ea"
integrity sha512-yQY9wpVNuiYhLLuyDlu1nBpqJELT1fGp7OctN4rW9I2W1T2p7A3tqPxsEzQprEwneQRBAlPM9vC8NsnMbct+pg==
"@tauri-apps/cli-darwin-arm64@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.1.tgz#d2e046c9bf54e77d47debf6ccf226c6d5b44e529"
integrity sha512-o2FSGj72gqJjlVtuScXQZUgiRs90PS9gG7YAz0Hgr4nV1MfIn9U6JVj6R+mnAEZBCK8qdy5jdemhmNKDDoiYQg==
"@tauri-apps/cli-darwin-x64@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.1.tgz#9c513499a65af2ed07945921a7e5656973b3cf4c"
integrity sha512-G1/v6AJPP5oIcjsOxZshag28wdmDx1Fis2yz545aUk7oKU86A3ZJpz0b8BaXkr93w04xGcmGAaspZeXMmTvrbw==
"@tauri-apps/cli-linux-arm-gnueabihf@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.1.tgz#1b9088a3fd1e7f98e571ae4ac64cce36bc32ec8e"
integrity sha512-hPDOUMviffyX5BySk6RuD7IZZeMuNUJzKWHxVWa0NHJPfxQOIPWwYWbk6TascrVk9GZYAImcB0yKfrll8I0VTg==
"@tauri-apps/cli-linux-arm64-gnu@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.1.tgz#93ae5f4231c61f6645630e338b761a38056b73ca"
integrity sha512-EJjTXqZchFLVrFgfxwstrQj7NwVDirffLhw5hRWS3L3Iys3IvqzIMVIA+GrY7KsaPwq7qaSqE1CDtP1wejE/9g==
"@tauri-apps/cli-linux-arm64-musl@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.1.tgz#fb1d6848ed24f9416fbec62d8a2603aa4bd94595"
integrity sha512-XgJIk0AcxRL4pWVjfj0wiC9WnIZoUIVLPcQs86dNxoqzwAvADdNYp+McXf3/MDxX8uGEzpgdvlqr4T+50c8f6w==
"@tauri-apps/cli-linux-x64-gnu@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.1.tgz#ff9e05828b707017caef2934c8a292cac979eeca"
integrity sha512-VoVrIn7b+F2n0LJoDkLGXQJsPV/U1h3QnjRNE+Tcju6xVPBx64H0vfb7lC3S4QfVpiQ4Uc+1UD3Slvn4jGpL/A==
"@tauri-apps/cli-linux-x64-musl@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.1.tgz#6cec6f33c35f927703bf0b17585e4604872d9105"
integrity sha512-tQi2K0LYW80BLud7ZFOy3WxCM2VjDRxuhxOYhtcLG39cIeGqsSz07LwiCFNBn4vy2J47TnZ+8XDRAOtxSFe25w==
"@tauri-apps/cli-win32-arm64-msvc@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.1.tgz#0315fd57efa6a54f9cb0144946dc626edc378acf"
integrity sha512-BdVwzVXY2JWBtWEO/G0//jIWXeWR52+KG0+kyoHO6QTxkncLrN5q2RldvXOe7CvhKe/qmgbkNosj5jWi7t49kQ==
"@tauri-apps/cli-win32-ia32-msvc@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.1.tgz#c3508bc8d4eb6d95becca02809d6bcbdd05d3d25"
integrity sha512-Q0ei4ZUHlGu/b4DP4Cm6WnI5zxpLxnf/vSwR2BYO3XO65TdLee1gTyuwYuSZJYu5jxqSoSusmLyL4F43jHhf9Q==
"@tauri-apps/cli-win32-x64-msvc@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.1.tgz#04f4af26589708752d711bfd1ef4dca21e2b3f7c"
integrity sha512-chpsJ5PIwMOdn1IIJ6bj2G7jv9jQryVvhujU0k3kt/5kE7OuLRDYbI5BAIzMOaLoOTgoo8oxcFXQ+enELSxlMQ==
"@tauri-apps/cli@^1.4.0":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.1.tgz#2aba9b320f1b79f160e7c011110ae5d2b25a8de2"
integrity sha512-Ssj30axil5vPBV3W5ScHXk4umTKu6BhfmMdljfKDOG9K55gAqzBAE2VBC5e/ouclSxZfN+6YNL9VhDXFu/UyeA==
optionalDependencies:
"@tauri-apps/cli-darwin-arm64" "1.5.1"
"@tauri-apps/cli-darwin-x64" "1.5.1"
"@tauri-apps/cli-linux-arm-gnueabihf" "1.5.1"
"@tauri-apps/cli-linux-arm64-gnu" "1.5.1"
"@tauri-apps/cli-linux-arm64-musl" "1.5.1"
"@tauri-apps/cli-linux-x64-gnu" "1.5.1"
"@tauri-apps/cli-linux-x64-musl" "1.5.1"
"@tauri-apps/cli-win32-arm64-msvc" "1.5.1"
"@tauri-apps/cli-win32-ia32-msvc" "1.5.1"
"@tauri-apps/cli-win32-x64-msvc" "1.5.1"
"@types/node@^18.7.10":
version "18.18.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.18.3.tgz#e5188135fc2909b46530c798ef49be65083be3fd"
integrity sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==
"@types/prop-types@*":
version "15.7.8"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.8.tgz#805eae6e8f41bd19e88917d2ea200dc992f405d3"
integrity sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==
"@types/react-dom@^18.0.6":
version "18.2.10"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.10.tgz#06247cb600e39b63a0a385f6a5014c44bab296f2"
integrity sha512-5VEC5RgXIk1HHdyN1pHlg0cOqnxHzvPGpMMyGAP5qSaDRmyZNDaQ0kkVAkK6NYlDhP6YBID3llaXlmAS/mdgCA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.0.15":
version "18.2.25"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.25.tgz#99fa44154132979e870ff409dc5b6e67f06f0199"
integrity sha512-24xqse6+VByVLIr+xWaQ9muX1B4bXJKXBbjszbld/UEDslGLY53+ZucF44HCmLbMPejTzGG9XgR+3m2/Wqu1kw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.4"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.4.tgz#fedc3e5b15c26dc18faae96bf1317487cb3658cf"
integrity sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==
"@vitejs/plugin-react@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-3.1.0.tgz#d1091f535eab8b83d6e74034d01e27d73c773240"
integrity sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==
dependencies:
"@babel/core" "^7.20.12"
"@babel/plugin-transform-react-jsx-self" "^7.18.6"
"@babel/plugin-transform-react-jsx-source" "^7.19.6"
magic-string "^0.27.0"
react-refresh "^0.14.0"
"@welldone-software/why-did-you-render@^7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@welldone-software/why-did-you-render/-/why-did-you-render-7.0.1.tgz#09f487d84844bd8e66435843c2e0305702e61efb"
integrity sha512-Qe/8Xxa2G+LMdI6VoazescPzjjkHYduCDa8aHOJR50e9Bgs8ihkfMBY+ev7B4oc3N59Zm547Sgjf8h5y0FOyoA==
dependencies:
lodash "^4"
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
browserslist@^4.21.9:
version "4.22.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619"
integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==
dependencies:
caniuse-lite "^1.0.30001541"
electron-to-chromium "^1.4.535"
node-releases "^2.0.13"
update-browserslist-db "^1.0.13"
caniuse-lite@^1.0.30001541:
version "1.0.30001543"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz#478a3e9dddbb353c5ab214b0ecb0dbed529ed1d8"
integrity sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
convert-source-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
csstype@^3.0.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b"
integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==
debug@^4.1.0:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
electron-to-chromium@^1.4.535:
version "1.4.540"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.540.tgz#c685f2f035e93eb21dd6a9cfe2c735bad8f77401"
integrity sha512-aoCqgU6r9+o9/S7wkcSbmPRFi7OWZWiXS9rtjEd+Ouyu/Xyw5RSq2XN8s5Qp8IaFOLiRrhQCphCIjAxgG3eCAg==
esbuild@^0.18.10:
version "0.18.20"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6"
integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==
optionalDependencies:
"@esbuild/android-arm" "0.18.20"
"@esbuild/android-arm64" "0.18.20"
"@esbuild/android-x64" "0.18.20"
"@esbuild/darwin-arm64" "0.18.20"
"@esbuild/darwin-x64" "0.18.20"
"@esbuild/freebsd-arm64" "0.18.20"
"@esbuild/freebsd-x64" "0.18.20"
"@esbuild/linux-arm" "0.18.20"
"@esbuild/linux-arm64" "0.18.20"
"@esbuild/linux-ia32" "0.18.20"
"@esbuild/linux-loong64" "0.18.20"
"@esbuild/linux-mips64el" "0.18.20"
"@esbuild/linux-ppc64" "0.18.20"
"@esbuild/linux-riscv64" "0.18.20"
"@esbuild/linux-s390x" "0.18.20"
"@esbuild/linux-x64" "0.18.20"
"@esbuild/netbsd-x64" "0.18.20"
"@esbuild/openbsd-x64" "0.18.20"
"@esbuild/sunos-x64" "0.18.20"
"@esbuild/win32-arm64" "0.18.20"
"@esbuild/win32-ia32" "0.18.20"
"@esbuild/win32-x64" "0.18.20"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
lodash@^4:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.1.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
magic-string@^0.27.0:
version "0.27.0"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.27.0.tgz#e4a3413b4bab6d98d2becffd48b4a257effdbbf3"
integrity sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.13"
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
nanoid@^3.3.6:
version "3.3.6"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
node-releases@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
postcss@^8.4.27:
version "8.4.31"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
dependencies:
nanoid "^3.3.6"
picocolors "^1.0.0"
source-map-js "^1.0.2"
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.23.0"
react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
rollup@^3.27.1:
version "3.29.4"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981"
integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==
optionalDependencies:
fsevents "~2.3.2"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
semver@^6.3.1:
version "6.3.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
typescript@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
update-browserslist-db@^1.0.13:
version "1.0.13"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
dependencies:
escalade "^3.1.1"
picocolors "^1.0.0"
vite@^4.2.1:
version "4.4.10"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.10.tgz#3794639cc433f7cb33ad286930bf0378c86261c8"
integrity sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"
rollup "^3.27.1"
optionalDependencies:
fsevents "~2.3.2"
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==