From b980f04bebe0790f311621cd7d641fdf828594d0 Mon Sep 17 00:00:00 2001 From: mslxl Date: Tue, 5 Dec 2023 02:14:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=AE=89=E8=A3=85=20msys2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + pnpm-lock.yaml | 60 +++++++++++ src-tauri/build.rs | 36 +++---- src-tauri/scripts/msys2.ps1 | 30 ++++++ src-tauri/src/ipc/cmd/host.rs | 5 + src-tauri/src/ipc/rt/checker.rs | 5 +- src-tauri/src/ipc/rt/runner.rs | 6 +- src-tauri/src/ipc/setup/installer.rs | 99 +++++++++++++++++++ src-tauri/src/ipc/setup/mod.rs | 4 +- src-tauri/src/main.rs | 39 ++++---- src-tauri/tauri.conf.json | 3 +- src/components/codemirror/index.tsx | 2 +- src/components/{pref => }/setup/setup-cxx.tsx | 30 +++++- src/components/{pref => }/setup/setup-py.tsx | 2 +- src/components/ui/progress.tsx | 26 +++++ src/components/ui/tooltip.tsx | 28 ++++++ src/hooks/useChangeLanguageDialog.tsx | 2 +- src/hooks/useGetLanguageCompiler.ts | 2 +- src/hooks/useZoom.ts | 2 +- src/lib/fs/installer.ts | 9 ++ src/lib/ipc/host.ts | 11 ++- src/lib/ipc/setup.ts | 2 + src/pages/Install/index.tsx | 92 +++++++++++++++++ src/pages/Main/menu-event.tsx | 2 +- src/pages/Main/tabbar.tsx | 2 +- src/pages/Preference/appearance.tsx | 2 +- src/pages/Preference/editor.tsx | 2 +- src/pages/Preference/language.tsx | 6 +- src/pages/Setup/index.tsx | 6 +- src/router.tsx | 5 + src/store/setting/index.ts | 55 ++++++----- src/store/system.ts | 6 ++ 32 files changed, 492 insertions(+), 91 deletions(-) create mode 100644 src-tauri/scripts/msys2.ps1 create mode 100644 src-tauri/src/ipc/setup/installer.rs rename src/components/{pref => }/setup/setup-cxx.tsx (52%) rename src/components/{pref => }/setup/setup-py.tsx (97%) create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/lib/fs/installer.ts create mode 100644 src/pages/Install/index.tsx create mode 100644 src/store/system.ts diff --git a/package.json b/package.json index 2650713..d7f5993 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,11 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tooltip": "^1.0.7", "@replit/codemirror-emacs": "^6.0.1", "@replit/codemirror-vim": "^6.1.0", "@tauri-apps/api": "^1.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ca53cb..c80871e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ dependencies: '@radix-ui/react-popover': specifier: ^1.0.7 version: 1.0.7(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-progress': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-select': specifier: ^2.0.0 version: 2.0.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -74,6 +77,9 @@ dependencies: '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-tooltip': + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) '@replit/codemirror-emacs': specifier: ^6.0.1 version: 6.0.1(@codemirror/autocomplete@6.11.0)(@codemirror/commands@6.3.0)(@codemirror/search@6.5.4)(@codemirror/state@6.3.1)(@codemirror/view@6.22.0) @@ -1789,6 +1795,28 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-progress@1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} peerDependencies: @@ -1925,6 +1953,38 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.23.2 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.37)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.0(react@18.2.0): resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} peerDependencies: diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 62b12b6..ecc2a4e 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -13,23 +13,23 @@ fn gcc(inp: &str, oup: &str) { .success()); } fn main() { - if cfg!(windows) { - gcc( - "src-util/consolepauser.windows.cpp", - "src-util/build/consolepauser", - ) - } else { - gcc( - "src-util/consolepauser.unix.cpp", - "src-util/build/consolepauser", - ) - } - gcc("src-util/ncmp.cpp", "src-util/build/ncmp"); - gcc("src-util/rcmp.cpp", "src-util/build/rcmp"); - gcc("src-util/rcmp4.cpp", "src-util/build/rcmp4"); - gcc("src-util/rcmp6.cpp", "src-util/build/rcmp6"); - gcc("src-util/rcmp9.cpp", "src-util/build/ncmp9"); - gcc("src-util/wcmp.cpp", "src-util/build/wcmp"); - gcc("src-util/yesno.cpp", "src-util/build/yesno"); + // if cfg!(windows) { + // gcc( + // "src-util/consolepauser.windows.cpp", + // "src-util/build/consolepauser", + // ) + // } else { + // gcc( + // "src-util/consolepauser.unix.cpp", + // "src-util/build/consolepauser", + // ) + // } + // gcc("src-util/ncmp.cpp", "src-util/build/ncmp"); + // gcc("src-util/rcmp.cpp", "src-util/build/rcmp"); + // gcc("src-util/rcmp4.cpp", "src-util/build/rcmp4"); + // gcc("src-util/rcmp6.cpp", "src-util/build/rcmp6"); + // gcc("src-util/rcmp9.cpp", "src-util/build/ncmp9"); + // gcc("src-util/wcmp.cpp", "src-util/build/wcmp"); + // gcc("src-util/yesno.cpp", "src-util/build/yesno"); tauri_build::build(); } diff --git a/src-tauri/scripts/msys2.ps1 b/src-tauri/scripts/msys2.ps1 new file mode 100644 index 0000000..dfcaf0e --- /dev/null +++ b/src-tauri/scripts/msys2.ps1 @@ -0,0 +1,30 @@ +$target = $args[0] +$downloadUrl = "https://mirrors.tuna.tsinghua.edu.cn/msys2/distrib/msys2-x86_64-latest.sfx.exe" +$mirror = 'sed -i "s#https\?://mirror.msys2.org/#https://mirrors.tuna.tsinghua.edu.cn/msys2/#g" /etc/pacman.d/mirrorlist*' + +$installer = "$($env:TEMP)\msys2.exe" + + +if (!(Test-Path $installer -PathType Leaf)) { + Write-Host "Download file to $($installer)" + Invoke-WebRequest -Uri $downloadUrl -OutFile $installer +} + +& $installer "-y", "-o$($target)" + +$msys2 = "$($target)/msys64/usr/bin/bash.exe" + +& $msys2 "-l", "-c", $mirror +& $msys2 "-l", "-c", "pacman --noconfirm -S mingw-w64-ucrt-x86_64-clang-tools-extra mingw-w64-ucrt-x86_64-gcc" + +Write-Host "Tools were installed to $($target)/msys64/ucrt64/bin" + +$report = @{ + 'gcc' = "$($target)\msys64\ucrt64\bin\g++.exe" + 'clangd' = "$($target)\msys64\ucrt64\bin\clangd.exe" +} + +# $report | ConvertTo-Json | Out-File "$($target)/msys2.json" -Encoding UTF8NoBOM +$reportJson = $report | ConvertTo-Json +[IO.File]::WriteAllLines("$($target)\msys2.json", $reportJson) +Remove-Item $installer \ No newline at end of file diff --git a/src-tauri/src/ipc/cmd/host.rs b/src-tauri/src/ipc/cmd/host.rs index af0544e..ff56b18 100644 --- a/src-tauri/src/ipc/cmd/host.rs +++ b/src-tauri/src/ipc/cmd/host.rs @@ -6,3 +6,8 @@ pub async fn get_hostname() -> Result { .to_owned(); Ok(name) } + +#[tauri::command] +pub async fn get_system_name() -> Result { + Ok(std::env::consts::OS.to_owned()) +} diff --git a/src-tauri/src/ipc/rt/checker.rs b/src-tauri/src/ipc/rt/checker.rs index c383d15..47ecbd7 100644 --- a/src-tauri/src/ipc/rt/checker.rs +++ b/src-tauri/src/ipc/rt/checker.rs @@ -68,9 +68,10 @@ pub async fn check_answer( let checker_path = match checker { CheckerType::Internal { name } => if cfg!(windows) { app.path_resolver() - .resolve_resource(format!("{}.exe", name)) + .resolve_resource(format!("sidecar/{}.exe", name)) } else { - app.path_resolver().resolve_resource(name) + app.path_resolver() + .resolve_resource(format!("sidecar/{}", name)) } .ok_or(String::from("no such the checker"))?, _ => unimplemented!(), diff --git a/src-tauri/src/ipc/rt/runner.rs b/src-tauri/src/ipc/rt/runner.rs index 594ea11..904672e 100644 --- a/src-tauri/src/ipc/rt/runner.rs +++ b/src-tauri/src/ipc/rt/runner.rs @@ -24,9 +24,9 @@ pub async fn run_detach( args: Vec, ) -> Result<(), String> { let pauser = if cfg!(windows) { - app.path_resolver().resolve_resource("consolepauser.exe") + app.path_resolver().resolve_resource("sidecar/consolepauser.exe") } else { - app.path_resolver().resolve_resource("consolepauser") + app.path_resolver().resolve_resource("sidecar/consolepauser") } .unwrap(); let mut cmd = std::process::Command::new(pauser); @@ -205,7 +205,7 @@ pub async fn run_redirect( Ok(result) } -struct ChildKiller(Child); +pub struct ChildKiller(Child); impl AsMut for ChildKiller { fn as_mut(&mut self) -> &mut Child { &mut self.0 diff --git a/src-tauri/src/ipc/setup/installer.rs b/src-tauri/src/ipc/setup/installer.rs new file mode 100644 index 0000000..a0979d9 --- /dev/null +++ b/src-tauri/src/ipc/setup/installer.rs @@ -0,0 +1,99 @@ +use std::{ + io::Stderr, + process::{Command, Stdio}, +}; + +use tauri::Runtime; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + sync::Mutex, +}; + +use crate::{util::console, RESOURCE_DIR}; + +#[derive(Default)] +pub struct PwshScriptState { + s: Mutex<()>, +} + +#[tauri::command] +pub async fn execuate_pwsh_script( + app: tauri::AppHandle, + window: tauri::Window, + state: tauri::State<'_, PwshScriptState>, + name: String, +) -> Result { + if !cfg!(windows) { + return Err(String::from("Installer only work on windows")); + } + let _guard = state.s.lock().await; + + let script_file = app + .path_resolver() + .resolve_resource(format!("sidecar/{}.ps1", &name)) + .unwrap(); + let script_file = dunce::canonicalize(script_file).unwrap(); + log::info!("execuate script {:?}", &script_file.to_str()); + let mut cmd = Command::new(which::which("powershell").unwrap()); + + let target = RESOURCE_DIR.get().unwrap(); + cmd.arg(&script_file.to_str().unwrap()); + cmd.arg(target.to_str().unwrap()); + console::hide_new_console(&mut cmd); + + let mut cmd = tokio::process::Command::from(cmd); + cmd.stdout(Stdio::piped()); + cmd.stderr(Stdio::piped()); + cmd.stdin(Stdio::piped()); + let mut proc = cmd.spawn().unwrap(); + let mut stdout = BufReader::new( + proc.stdout + .take() + .ok_or(String::from("Fail to open stdout"))?, + ) + .lines(); + let mut stderr = BufReader::new( + proc.stderr + .take() + .ok_or(String::from("Fail to open stderr"))?, + ) + .lines(); + + let mut stdout_eof = false; + let mut stderr_eof = false; + let result = loop { + tokio::select! { + Ok(data) = stdout.next_line(), if !stdout_eof => { + if let Some(line) = data { + window.emit("install_message", line).map_err(|e| e.to_string())?; + }else{ + stdout_eof = true + } + } + Ok(data) = stderr.next_line(), if !stderr_eof => { + if let Some(line) = data{ + window.emit("install_message", line).map_err(|e| e.to_string())?; + }else{ + stderr_eof = true; + } + } + else => { + break proc.wait().await.map_err(|e|e.to_string())?; + } + } + }; + + if result.success() { + let report = target.join(format!("{}.json", name)); + let content = tokio::fs::read_to_string(report) + .await + .map_err(|e| e.to_string())?; + Ok(content) + } else { + if let Some(code) = result.code() { + Err(format!("Process exit with code {}", code)) + } else { + Err(String::from("Process exit with terminal signal")) + } + } +} diff --git a/src-tauri/src/ipc/setup/mod.rs b/src-tauri/src/ipc/setup/mod.rs index cafa085..d0cefca 100644 --- a/src-tauri/src/ipc/setup/mod.rs +++ b/src-tauri/src/ipc/setup/mod.rs @@ -1,4 +1,6 @@ -use std::{time::Duration, process::Stdio}; +pub mod installer; + +use std::{process::Stdio, time::Duration}; use serde::{Deserialize, Serialize}; use tokio::process::Command; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index adf3fc9..183d04d 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -6,30 +6,22 @@ use std::{env, path::PathBuf}; use ipc::{ cmd::{bind::LSPState, competitive_companion::CompetitiveCompanionState}, rt::{checker::CheckerState, compiler::CompilerState, runner::RunnerState}, + setup::installer::PwshScriptState, }; use log::LevelFilter; -use once_cell::sync::{Lazy, OnceCell}; +use once_cell::sync::OnceCell; use tauri::{plugin::TauriPlugin, Manager, Runtime}; use tauri_plugin_log::{fern::colors::ColoredLevelConfig, LogTarget}; pub mod ipc; pub mod util; -static CURRENT_DIR: Lazy> = Lazy::new(|| { - let cell = OnceCell::new(); - cell.set(env::current_exe().unwrap().parent().unwrap().to_path_buf()) - .unwrap(); - cell -}); +pub static CONFIG_DIR: OnceCell = OnceCell::new(); +pub static RESOURCE_DIR: OnceCell = OnceCell::new(); fn log_pugin() -> TauriPlugin { - let log_dir = CURRENT_DIR.get().unwrap().join("logs"); let builder = tauri_plugin_log::Builder::default() - .targets([ - LogTarget::Stdout, - LogTarget::Webview, - LogTarget::Folder(log_dir), - ]) + .targets([LogTarget::Stdout, LogTarget::Webview, LogTarget::LogDir]) .with_colors(ColoredLevelConfig::default()) .max_file_size(10240); let builder = if cfg!(debug_assertions) { @@ -47,13 +39,7 @@ async fn is_debug() -> Result { #[tauri::command] async fn get_settings_path() -> Result { - Ok(CURRENT_DIR - .get() - .unwrap() - .join("settings.dat") - .to_str() - .unwrap() - .to_owned()) + Ok(String::from("settings.dat")) } fn main() { @@ -61,12 +47,21 @@ fn main() { .plugin(log_pugin()) .plugin(tauri_plugin_store::Builder::default().build()) .setup(|app| { - // provlegisto statue + // provlegisto state + CONFIG_DIR + .set(dunce::canonicalize(app.path_resolver().app_config_dir().unwrap()).unwrap()) + .unwrap(); + RESOURCE_DIR + .set( + dunce::canonicalize(app.path_resolver().app_local_data_dir().unwrap()).unwrap(), + ) + .unwrap(); app.manage(LSPState::default()); app.manage(CompetitiveCompanionState::default()); app.manage(CompilerState::default()); app.manage(RunnerState::default()); app.manage(CheckerState::default()); + app.manage(PwshScriptState::default()); Ok(()) }) .invoke_handler(tauri::generate_handler![ @@ -77,8 +72,10 @@ fn main() { ipc::cmd::competitive_companion::enable_competitive_companion, ipc::cmd::competitive_companion::disable_competitive_companion, ipc::cmd::host::get_hostname, + ipc::cmd::host::get_system_name, ipc::setup::capture_output, ipc::setup::which, + ipc::setup::installer::execuate_pwsh_script, ipc::rt::compiler::compile_source, ipc::rt::runner::run_detach, ipc::rt::runner::run_redirect, diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index bcc734e..e335b85 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -33,7 +33,8 @@ "identifier": "com.mslxl.provlegisto", "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], "resources": { - "src-util/build/*": "." + "src-util/build/*": "./sidecar/", + "scripts/*": "./sidecar/" } }, "security": { diff --git a/src/components/codemirror/index.tsx b/src/components/codemirror/index.tsx index f923135..b9117a0 100644 --- a/src/components/codemirror/index.tsx +++ b/src/components/codemirror/index.tsx @@ -12,7 +12,7 @@ import { useImmerAtom } from "jotai-immer" import { map } from "lodash" import "@fontsource/jetbrains-mono" import { filterCSSQuote } from "@/lib/utils" -import { editorFontFamily, editorFontSizeAtom } from "@/store/setting" +import { editorFontFamily, editorFontSizeAtom } from "@/store/setting/ui" type CodemirrorProps = { className?: string diff --git a/src/components/pref/setup/setup-cxx.tsx b/src/components/setup/setup-cxx.tsx similarity index 52% rename from src/components/pref/setup/setup-cxx.tsx rename to src/components/setup/setup-cxx.tsx index 5911165..4a39d93 100644 --- a/src/components/pref/setup/setup-cxx.tsx +++ b/src/components/setup/setup-cxx.tsx @@ -1,10 +1,32 @@ import PrefProgram from "@/components/pref/Program" +import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" -import { clangdPathAtom, clangdVersionAtom, enableCxxAtom, gccPathAtom, gccVersionAtom } from "@/store/setting" -import { useAtom } from "jotai" +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { SystemName } from "@/lib/ipc/host" +import { clangdPathAtom, clangdVersionAtom, enableCxxAtom, gccPathAtom, gccVersionAtom } from "@/store/setting/setup" +import { systemNameAtom } from "@/store/system" +import { useAtom, useAtomValue } from "jotai" +import { useNavigate } from "react-router-dom" export default function SetupCXX() { const [enableCxx, setEnableCxx] = useAtom(enableCxxAtom) + const systemName = useAtomValue(systemNameAtom) + const navigate = useNavigate() + const btnInstall = ( + + + + + + +

Only work on windows

+

Download MSYS2 and install it automatically

+
+
+
+ ) return (
@@ -21,7 +43,7 @@ export default function SetupCXX() { versionAtom={gccVersionAtom as any} versionFallback="File not found" > - {/* */} + {btnInstall} - {/* */} + {btnInstall} )} diff --git a/src/components/pref/setup/setup-py.tsx b/src/components/setup/setup-py.tsx similarity index 97% rename from src/components/pref/setup/setup-py.tsx rename to src/components/setup/setup-py.tsx index c77b63d..09713f4 100644 --- a/src/components/pref/setup/setup-py.tsx +++ b/src/components/setup/setup-py.tsx @@ -6,7 +6,7 @@ import { pyrightsVersionAtom, pythonPathAtom, pythonVersionAtom, -} from "@/store/setting" +} from "@/store/setting/setup" import { useAtom } from "jotai" export default function SetupPy() { diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..105fb65 --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,26 @@ +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..e121f0a --- /dev/null +++ b/src/components/ui/tooltip.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as TooltipPrimitive from "@radix-ui/react-tooltip" + +import { cn } from "@/lib/utils" + +const TooltipProvider = TooltipPrimitive.Provider + +const Tooltip = TooltipPrimitive.Root + +const TooltipTrigger = TooltipPrimitive.Trigger + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)) +TooltipContent.displayName = TooltipPrimitive.Content.displayName + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/src/hooks/useChangeLanguageDialog.tsx b/src/hooks/useChangeLanguageDialog.tsx index 141b8bd..0e24afe 100644 --- a/src/hooks/useChangeLanguageDialog.tsx +++ b/src/hooks/useChangeLanguageDialog.tsx @@ -1,7 +1,7 @@ import PrefSelect from "@/components/pref/Select" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" -import { availableLanguageListAtom } from "@/store/setting" +import { availableLanguageListAtom } from "@/store/setting/setup" import { PrimitiveAtom, useAtomValue } from "jotai" import { ReactNode, useState } from "react" diff --git a/src/hooks/useGetLanguageCompiler.ts b/src/hooks/useGetLanguageCompiler.ts index 9527510..311e044 100644 --- a/src/hooks/useGetLanguageCompiler.ts +++ b/src/hooks/useGetLanguageCompiler.ts @@ -1,4 +1,4 @@ -import { gccPathAtom, pythonPathAtom } from "@/store/setting" +import { gccPathAtom, pythonPathAtom } from "@/store/setting/setup" import useReadAtom from "./useReadAtom" import { LanguageMode } from "@/lib/ipc" diff --git a/src/hooks/useZoom.ts b/src/hooks/useZoom.ts index b11caa6..bc92c0f 100644 --- a/src/hooks/useZoom.ts +++ b/src/hooks/useZoom.ts @@ -1,4 +1,4 @@ -import { zoomStateAtom } from "@/store/setting" +import { zoomStateAtom } from "@/store/setting/ui" import { useAtom } from "jotai" import { useEffect } from "react" diff --git a/src/lib/fs/installer.ts b/src/lib/fs/installer.ts new file mode 100644 index 0000000..5fbdeab --- /dev/null +++ b/src/lib/fs/installer.ts @@ -0,0 +1,9 @@ +import { execuatePwshScript } from "../ipc/setup" + +type Msys2Report = { + gcc: string + clangd: string +} +export async function installMsys2(): Promise { + return execuatePwshScript("msys2").then((res) => JSON.parse(res)) +} diff --git a/src/lib/ipc/host.ts b/src/lib/ipc/host.ts index cc40603..ad57cbf 100644 --- a/src/lib/ipc/host.ts +++ b/src/lib/ipc/host.ts @@ -1,3 +1,12 @@ import { invoke } from "@tauri-apps/api" -export const getHostname: () => Promise = async () => invoke("get_hostname") +export const getHostname: () => Promise = () => invoke("get_hostname") + +export enum SystemName { + linux = "linux", + windows = "windows", + macos = "macos", + unknown = "", +} + +export const getSystemName: () => Promise = () => invoke("get_system_name") diff --git a/src/lib/ipc/setup.ts b/src/lib/ipc/setup.ts index 8093aca..f83496d 100644 --- a/src/lib/ipc/setup.ts +++ b/src/lib/ipc/setup.ts @@ -12,3 +12,5 @@ export const captureOutput: (program: string, args: string[]) => Promise invoke("execuate_pwsh_script", { name }) diff --git a/src/pages/Install/index.tsx b/src/pages/Install/index.tsx new file mode 100644 index 0000000..16e13ff --- /dev/null +++ b/src/pages/Install/index.tsx @@ -0,0 +1,92 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, +} from "@/components/ui/alert-dialog" +import { Progress } from "@/components/ui/progress" +import { useTauriEvent } from "@/hooks/useTauriEvent" +import { installMsys2 } from "@/lib/fs/installer" +import { clangdPathAtom, gccPathAtom } from "@/store/setting/setup" +import { AlertDialogTitle } from "@radix-ui/react-alert-dialog" +import { useSetAtom } from "jotai" +import { useEffect, useRef, useState } from "react" +import { VscClose } from "react-icons/vsc" +import { useNavigate, useParams } from "react-router-dom" + +export default function Install() { + const params = useParams() + const installName = params.name + const navigate = useNavigate() + const setGcc = useSetAtom(gccPathAtom) + const setClangd = useSetAtom(clangdPathAtom) + const [fatal, setFatal] = useState(false) + + const [dialogMessage, setDialogMessage] = useState("") + const [dialogVisible, setDialogVisible] = useState(false) + const [output, setOutput] = useState("") + const running = useRef(false) + + useEffect(() => { + if (running.current) return + running.current = true + if (params.name == "msys2") { + installMsys2() + .then((res) => { + setGcc(res.gcc) + setClangd(res.clangd) + setDialogMessage("Install complete!") + setDialogVisible(true) + }) + .catch((e) => { + setDialogMessage(e) + setDialogVisible(true) + setFatal(true) + }) + } else { + setDialogMessage("No such installer") + setDialogVisible(true) + } + }, []) + + useTauriEvent( + "install_message", + (msg) => { + setOutput((pre) => `${pre}\n${msg.payload}`) + }, + [setOutput], + ) + + return ( +
+ + + + Error + {dialogMessage} + + + See Logs + navigate(-1)}>Back + + + +
+ {!fatal ? null : ( + + )} +

Installing {installName}

+ Girl in Prayer... +
+ +
+
{output}
+
+
+ ) +} diff --git a/src/pages/Main/menu-event.tsx b/src/pages/Main/menu-event.tsx index fe85b75..198f717 100644 --- a/src/pages/Main/menu-event.tsx +++ b/src/pages/Main/menu-event.tsx @@ -1,7 +1,7 @@ import { useMitt } from "@/hooks/useMitt" import useReadAtom from "@/hooks/useReadAtom" import { openProblem, saveProblem } from "@/lib/fs/problem" -import { defaultLanguageAtom } from "@/store/setting" +import { defaultLanguageAtom } from "@/store/setting/setup" import { activeIdAtom, counterAtom, diff --git a/src/pages/Main/tabbar.tsx b/src/pages/Main/tabbar.tsx index 3b2968e..5562b57 100644 --- a/src/pages/Main/tabbar.tsx +++ b/src/pages/Main/tabbar.tsx @@ -31,7 +31,7 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog" import useChangeLanguageDialog from "@/hooks/useChangeLanguageDialog" -import { defaultLanguageAtom } from "@/store/setting" +import { defaultLanguageAtom } from "@/store/setting/setup" const HorizontalUnorderedList = styled.ul` position: relative; diff --git a/src/pages/Preference/appearance.tsx b/src/pages/Preference/appearance.tsx index 01637b4..7bacd7c 100644 --- a/src/pages/Preference/appearance.tsx +++ b/src/pages/Preference/appearance.tsx @@ -1,5 +1,5 @@ import { PrefNumber } from "@/components/pref" -import { zoomStateAtom } from "@/store/setting" +import { zoomStateAtom } from "@/store/setting/ui" export default function Page() { return ( diff --git a/src/pages/Preference/editor.tsx b/src/pages/Preference/editor.tsx index 2520130..a1b547d 100644 --- a/src/pages/Preference/editor.tsx +++ b/src/pages/Preference/editor.tsx @@ -1,7 +1,7 @@ import { PrefNumber } from "@/components/pref" import { PrefText } from "@/components/pref/Text" import { filterCSSQuote } from "@/lib/utils" -import { editorFontFamily, editorFontSizeAtom } from "@/store/setting" +import { editorFontFamily, editorFontSizeAtom } from "@/store/setting/ui" import { useAtomValue } from "jotai" import styled from "styled-components" diff --git a/src/pages/Preference/language.tsx b/src/pages/Preference/language.tsx index 245fbd0..3ca8f15 100644 --- a/src/pages/Preference/language.tsx +++ b/src/pages/Preference/language.tsx @@ -1,7 +1,7 @@ import PrefSelect from "@/components/pref/Select" -import SetupCXX from "@/components/pref/setup/setup-cxx" -import SetupPy from "@/components/pref/setup/setup-py" -import { availableLanguageListAtom, defaultLanguageAtom } from "@/store/setting" +import SetupCXX from "@/components/setup/setup-cxx" +import SetupPy from "@/components/setup/setup-py" +import { availableLanguageListAtom, defaultLanguageAtom } from "@/store/setting/setup" import { useAtomValue } from "jotai" export default function Page() { diff --git a/src/pages/Setup/index.tsx b/src/pages/Setup/index.tsx index f179ddf..f73116a 100644 --- a/src/pages/Setup/index.tsx +++ b/src/pages/Setup/index.tsx @@ -10,14 +10,14 @@ import { pyrightsVersionAtom, pythonVersionAtom, setupDeviceAtom, -} from "@/store/setting" +} from "@/store/setting/setup" import { useAtom, useAtomValue } from "jotai" import { useEffect, useState } from "react" import { useNavigate } from "react-router-dom" import * as log from "tauri-plugin-log-api" import Logo from "./logo" -import SetupCXX from "../../components/pref/setup/setup-cxx" -import SetupPy from "../../components/pref/setup/setup-py" +import SetupCXX from "../../components/setup/setup-cxx" +import SetupPy from "../../components/setup/setup-py" import { Button } from "@/components/ui/button" import useReadAtom from "@/hooks/useReadAtom" import { diff --git a/src/router.tsx b/src/router.tsx index 23ef10b..8201258 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -11,6 +11,7 @@ const PagePreference = { Language: lazy(() => import("@/pages/Preference/language")), } const PageSetup = lazy(() => import("@/pages/Setup")) +const PageInstall = lazy(() => import("@/pages/Install")) const PageAbout = lazy(() => import("@/pages/About")) const router = createBrowserRouter([ @@ -52,6 +53,10 @@ const router = createBrowserRouter([ path: "/setup", element: , }, + { + path: "/install/:name", + element: , + }, ]) export default function Router() { diff --git a/src/store/setting/index.ts b/src/store/setting/index.ts index 37f456e..1fdb9c8 100644 --- a/src/store/setting/index.ts +++ b/src/store/setting/index.ts @@ -1,6 +1,3 @@ -export * from "./ui" -export * from "./keymap" -export * from "./setup" import * as log from "tauri-plugin-log-api" import { Store } from "tauri-plugin-store-api" import { atomWithStorage } from "jotai/utils" @@ -10,31 +7,39 @@ let store: Store | null = null export async function loadSettingsStore() { const path = await getSettingsPath() store = new Store(path) + return } export function atomWithSettings(key: string, initialValue: T) { - const atom = atomWithStorage(key, initialValue, { - async getItem(key, initialValue) { - if (store == null) throw new Error("Store not init") - return (await store.get(key)) ?? initialValue + const atom = atomWithStorage( + key, + initialValue, + { + async getItem(key, initialValue) { + if (store == null) throw new Error("Store not init") + return (await store.get(key)) ?? initialValue + }, + async setItem(key, newValue) { + if (store == null) throw new Error("Store not init") + await store!.set(key, newValue) + }, + async removeItem(key) { + if (store == null) throw new Error("Store not init") + await store.delete(key) + }, + subscribe(key, callback, initialValue) { + const unlisten = store!.onChange((k, value) => { + if (key == k && value != initialValue) callback(value as T) + }) + return () => { + unlisten.then((fn) => fn()).catch((reson) => log.error(reson)) + } + }, }, - async setItem(key, newValue) { - if (store == null) throw new Error("Store not init") - await store!.set(key, newValue) + { + unstable_getOnInit: true, }, - async removeItem(key) { - if (store == null) throw new Error("Store not init") - await store.delete(key) - }, - subscribe(key, callback, initialValue) { - const unlisten = store!.onChange((k, value) => { - if (key == k && value != initialValue) callback(value as T) - }) - return () => { - unlisten.then((fn) => fn()).catch((reson) => log.error(reson)) - } - }, - }) + ) atom.debugLabel = `settings.${key}` - return atom -} + return atom +} \ No newline at end of file diff --git a/src/store/system.ts b/src/store/system.ts new file mode 100644 index 0000000..c123c0a --- /dev/null +++ b/src/store/system.ts @@ -0,0 +1,6 @@ +import { getSystemName } from "@/lib/ipc/host" +import { atom } from "jotai" + +export const systemNameAtom = atom(async () => { + return getSystemName() +})