From 58293ed92072fc4b8106aa874f7da9425ab3e8c3 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 31 Aug 2024 17:08:00 +0900 Subject: [PATCH 01/43] refactor: add import alias --- package-lock.json | 18 +++++++++++++ package.json | 1 + src/atom/main.ts | 2 +- src/components/LineChart.tsx | 2 +- src/components/Sample.tsx | 4 +-- src/services/settingService.ts | 2 +- src/template/Chart.tsx | 10 +++---- tsconfig.json | 8 ++++-- vite.config.ts | 48 +++++++++++++++++++--------------- 9 files changed, 62 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 32ec561..c5524ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "devDependencies": { "@biomejs/biome": "1.8.3", "@tauri-apps/cli": "^1", + "@types/node": "^22.5.1", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.2.1", @@ -1533,6 +1534,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.1.tgz", + "integrity": "sha512-KkHsxej0j9IW1KKOOAA/XBA0z08UFSrRQHErzEfA3Vgq57eXIMYboIlHJuYIfd+lwCQjtKqUu3UnmKbtUc9yRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -3629,6 +3640,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", diff --git a/package.json b/package.json index b80456d..c6a5506 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "devDependencies": { "@biomejs/biome": "1.8.3", "@tauri-apps/cli": "^1", + "@types/node": "^22.5.1", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.2.1", diff --git a/src/atom/main.ts b/src/atom/main.ts index 8cbeb36..db44c5e 100644 --- a/src/atom/main.ts +++ b/src/atom/main.ts @@ -1,4 +1,4 @@ +import type { Settings } from "@/types/settingsType"; import { atom } from "jotai"; -import type { Settings } from "../types/settingsType"; export const settingsAtom = atom(null); diff --git a/src/components/LineChart.tsx b/src/components/LineChart.tsx index 14cd87c..dac664b 100644 --- a/src/components/LineChart.tsx +++ b/src/components/LineChart.tsx @@ -1,3 +1,4 @@ +import type { ChartDataType } from "@/types/chartType"; import { Cpu, GraphicsCard, Memory } from "@phosphor-icons/react"; import { CategoryScale, @@ -13,7 +14,6 @@ import { import type { Chart, ChartData } from "chart.js"; import { useRef } from "react"; import { Line } from "react-chartjs-2"; -import type { ChartDataType } from "../types/chartType"; import CustomLegend, { type LegendItem } from "./CustomLegend"; ChartJS.register( diff --git a/src/components/Sample.tsx b/src/components/Sample.tsx index eebd461..955d86e 100644 --- a/src/components/Sample.tsx +++ b/src/components/Sample.tsx @@ -1,4 +1,3 @@ -import { useEffect, useState } from "react"; import { getCpuMemoryHistory, getCpuUsage, @@ -6,7 +5,8 @@ import { getGpuUsage, getGpuUsageHistory, getMemoryUsage, -} from "../services/hardwareService"; +} from "@/services/hardwareService"; +import { useEffect, useState } from "react"; const Sample = () => { const [cpuUsage, setCpuUsage] = useState(0); diff --git a/src/services/settingService.ts b/src/services/settingService.ts index d858d88..d9dfd59 100644 --- a/src/services/settingService.ts +++ b/src/services/settingService.ts @@ -1,5 +1,5 @@ +import type { Settings } from "@/types/settingsType"; import { invoke } from "@tauri-apps/api/tauri"; -import type { Settings } from "../types/settingsType"; export const getSettings = async (): Promise => { return await invoke("get_settings"); diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index 61317bd..f704487 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -1,12 +1,12 @@ -import { useAtom } from "jotai"; -import { useCallback, useEffect, useMemo, useState } from "react"; -import { settingsAtom } from "../atom/main"; -import LineChart from "../components/LineChart"; +import { settingsAtom } from "@/atom/main"; +import LineChart from "@/components/LineChart"; import { getCpuMemoryHistory, getCpuUsageHistory, getGpuUsageHistory, -} from "../services/hardwareService"; +} from "@/services/hardwareService"; +import { useAtom } from "jotai"; +import { useCallback, useEffect, useMemo, useState } from "react"; const ChartTemplate = () => { const [cpuData, setCpuData] = useState([]); diff --git a/tsconfig.json b/tsconfig.json index a7fc6fb..5f9a283 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,8 +18,12 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "baseUrl": "./src", + "paths": { + "@/*": ["*"] + } }, "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + "references": [{ "path": "./tsconfig.node.json" }], } diff --git a/vite.config.ts b/vite.config.ts index e1eb191..6ae1b59 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,21 +1,27 @@ -import react from "@vitejs/plugin-react"; -import { defineConfig } from "vite"; - -// 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` - // - // 1. prevent vite from obscuring rust errors - clearScreen: false, - // 2. tauri expects a fixed port, fail if that port is not available - server: { - port: 1520, - strictPort: true, - watch: { - // 3. tell vite to ignore watching `src-tauri` - ignored: ["**/src-tauri/**"], - }, - }, -})); +import path from "node:path"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +// 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` + // + // 1. prevent vite from obscuring rust errors + clearScreen: false, + // 2. tauri expects a fixed port, fail if that port is not available + server: { + port: 1520, + strictPort: true, + watch: { + // 3. tell vite to ignore watching `src-tauri` + ignored: ["**/src-tauri/**"], + }, + }, + resolve: { + alias: { + "@": path.resolve(__dirname, "src"), + }, + }, +})); From c4b3aac529eb151f3e47e0be8b604f211b470cac Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 31 Aug 2024 19:01:23 +0900 Subject: [PATCH 02/43] =?UTF-8?q?update:=20=E3=83=8F=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=A6=E3=82=A7=E3=82=A2=E4=BD=BF=E7=94=A8=E7=8E=87=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E5=87=A6=E7=90=86=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom/chart.ts | 5 ++ src/atom/main.ts | 6 ++- src/consts/chart.ts | 6 +++ src/hooks/useHardwareData.ts | 61 ++++++++++++++++++++++ src/template/Chart.tsx | 99 +++++++++++++++--------------------- src/types/settingsType.ts | 4 +- 6 files changed, 122 insertions(+), 59 deletions(-) create mode 100644 src/atom/chart.ts create mode 100644 src/consts/chart.ts create mode 100644 src/hooks/useHardwareData.ts diff --git a/src/atom/chart.ts b/src/atom/chart.ts new file mode 100644 index 0000000..6b2128f --- /dev/null +++ b/src/atom/chart.ts @@ -0,0 +1,5 @@ +import { atom } from "jotai"; + +export const cpuUsageHistoryAtom = atom([]); +export const memoryUsageHistoryAtom = atom([]); +export const graphicUsageHistoryAtom = atom([]); diff --git a/src/atom/main.ts b/src/atom/main.ts index db44c5e..a7ef9c5 100644 --- a/src/atom/main.ts +++ b/src/atom/main.ts @@ -1,4 +1,8 @@ import type { Settings } from "@/types/settingsType"; import { atom } from "jotai"; -export const settingsAtom = atom(null); +export const settingsAtom = atom({ + language: "en", + theme: "light", + display_targets: [], +}); diff --git a/src/consts/chart.ts b/src/consts/chart.ts new file mode 100644 index 0000000..bd3d409 --- /dev/null +++ b/src/consts/chart.ts @@ -0,0 +1,6 @@ +export const chartConfig = { + /** + * グラフの履歴の長さ(秒) + */ + historyLengthSec: 60, +} as const; diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts new file mode 100644 index 0000000..066515f --- /dev/null +++ b/src/hooks/useHardwareData.ts @@ -0,0 +1,61 @@ +import { + cpuUsageHistoryAtom, + graphicUsageHistoryAtom, + memoryUsageHistoryAtom, +} from "@/atom/chart"; +import { chartConfig } from "@/consts/chart"; +import { + getCpuUsage, + getGpuUsage, + getMemoryUsage, +} from "@/services/hardwareService"; +import type { ChartDataType } from "@/types/chartType"; +import { type PrimitiveAtom, useSetAtom } from "jotai"; +import { useEffect } from "react"; + +type AtomActionMapping = { + atom: PrimitiveAtom; + action: () => Promise; +}; + +/** + * ハードウェア使用率の履歴を更新する + */ +export const useUsageUpdater = (dataType: ChartDataType) => { + const mapping: Record = { + cpu: { + atom: cpuUsageHistoryAtom, + action: getCpuUsage, + }, + memory: { + atom: memoryUsageHistoryAtom, + action: getMemoryUsage, + }, + gpu: { + atom: graphicUsageHistoryAtom, + action: getGpuUsage, + }, + }; + + const setHistory = useSetAtom(mapping[dataType].atom); + const getUsage = mapping[dataType].action; + + useEffect(() => { + const intervalId = setInterval(async () => { + const usage = await getUsage(); + setHistory((prev) => { + const newHistory = [...prev, usage]; + + // 履歴保持数に満たない場合は0で埋める + const paddedHistory = Array( + Math.max(chartConfig.historyLengthSec - newHistory.length, 0), + ) + .fill(null) + .concat(newHistory); + return paddedHistory.slice(-chartConfig.historyLengthSec); + }); + }, 1000); + + return () => clearInterval(intervalId); + }, [setHistory, getUsage]); +}; diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index f704487..8c731b5 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -1,76 +1,61 @@ +import { + cpuUsageHistoryAtom, + graphicUsageHistoryAtom, + memoryUsageHistoryAtom, +} from "@/atom/chart"; import { settingsAtom } from "@/atom/main"; import LineChart from "@/components/LineChart"; -import { - getCpuMemoryHistory, - getCpuUsageHistory, - getGpuUsageHistory, -} from "@/services/hardwareService"; +import { chartConfig } from "@/consts/chart"; +import { useUsageUpdater } from "@/hooks/useHardwareData"; + import { useAtom } from "jotai"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useMemo } from "react"; -const ChartTemplate = () => { - const [cpuData, setCpuData] = useState([]); - const [memoryData, setMemoryData] = useState([]); - const [gpuData, setGpuData] = useState([]); - const [labels, setLabels] = useState([]); +const labels = Array(chartConfig.historyLengthSec).fill(""); - const [settings] = useAtom(settingsAtom); +const CpuUsageChart = () => { + const [cpuUsageHistory] = useAtom(cpuUsageHistoryAtom); + useUsageUpdater("cpu"); - const fetchData = useCallback(async () => { - const seconds = 60; + return ( + + ); +}; - const newCpuDataPromise = settings?.display_targets.includes("cpu") - ? getCpuUsageHistory(seconds) - : Promise.resolve([]); - const newMemoryDataPromise = settings?.display_targets.includes("memory") - ? getCpuMemoryHistory(seconds) - : Promise.resolve([]); - const newGpuDataPromise = settings?.display_targets.includes("gpu") - ? getGpuUsageHistory(seconds) - : Promise.resolve([]); +const MemoryUsageChart = () => { + const [memoryUsageHistory] = useAtom(memoryUsageHistoryAtom); + useUsageUpdater("memory"); + + return ( + + ); +}; - const [newCpuData, newMemoryData, newGpuData] = await Promise.all([ - newCpuDataPromise, - newMemoryDataPromise, - newGpuDataPromise, - ]); +const GpuUsageChart = () => { + const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); + useUsageUpdater("gpu"); - if (cpuData.length < seconds) { - setCpuData([...cpuData, newCpuData[newCpuData.length - 1]]); - setMemoryData([...memoryData, newMemoryData[newMemoryData.length - 1]]); - setGpuData([...gpuData, newGpuData[newGpuData.length - 1]]); - setLabels([...labels, ""]); - } else { - setCpuData([...cpuData.slice(1), newCpuData[newCpuData.length - 1]]); - setMemoryData([ - ...memoryData.slice(1), - newMemoryData[newMemoryData.length - 1], - ]); - setGpuData([...gpuData.slice(1), newGpuData[newGpuData.length - 1]]); - setLabels([...labels.slice(1), ""]); - } - }, [settings, cpuData, memoryData, gpuData, labels]); + return ( + + ); +}; - useEffect(() => { - const interval = setInterval(fetchData, 1000); - return () => clearInterval(interval); - }, [fetchData]); +const ChartTemplate = () => { + const [settings] = useAtom(settingsAtom); const renderedCharts = useMemo(() => { return ( <> - {settings?.display_targets.includes("cpu") && ( - - )} - {settings?.display_targets.includes("memory") && ( - - )} - {settings?.display_targets.includes("gpu") && ( - - )} + {settings?.display_targets.includes("cpu") && } + {settings?.display_targets.includes("memory") && } + {settings?.display_targets.includes("gpu") && } ); - }, [labels, cpuData, memoryData, gpuData, settings]); + }, [settings]); return
{renderedCharts}
; }; diff --git a/src/types/settingsType.ts b/src/types/settingsType.ts index 7296922..b618b03 100644 --- a/src/types/settingsType.ts +++ b/src/types/settingsType.ts @@ -1,5 +1,7 @@ +import type { ChartDataType } from "./chartType"; + export type Settings = { language: string; theme: "light" | "dark"; - display_targets: "cpu" | "memory" | "gpu"; + display_targets: Array; }; From e4f32e3c85a1702eaa83a8fbbf23e905c749c811 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 31 Aug 2024 19:21:49 +0900 Subject: [PATCH 03/43] update: `ci.yaml` --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10d6ef9..384e775 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,6 +5,9 @@ on: branches: - master - develop + push: + branches: + - develop jobs: test-tauri: From 956592da14f87e36076b745cfb0facc95bc57e5c Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 12:01:47 +0900 Subject: [PATCH 04/43] =?UTF-8?q?add:=20GPU=E4=BD=BF=E7=94=A8=E7=8E=87?= =?UTF-8?q?=E5=8F=96=E5=BE=97=E9=96=A2=E6=95=B0=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 7 +- src-tauri/Cargo.lock | 4 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands/config.rs | 6 +- src-tauri/src/commands/hardware.rs | 63 ++--------- src-tauri/src/main.rs | 121 +++++++++++----------- src-tauri/src/services/graphic_service.rs | 71 +++++++++++++ src-tauri/src/services/mod.rs | 1 + 8 files changed, 153 insertions(+), 122 deletions(-) create mode 100644 src-tauri/src/services/graphic_service.rs create mode 100644 src-tauri/src/services/mod.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index a0966b1..e9699e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,10 @@ "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, "editor.inlayHints.enabled": "off" - } + }, + "cSpell.words": [ + "consts", + "nvapi", + "tauri" + ] } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 391e700..2b8f2e0 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3063,9 +3063,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 13813b3..0324f1f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -33,7 +33,7 @@ tauri = { version = "1.6.5", features = [] } sysinfo = "0.31.3" tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } nvapi = "=0.1.4" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1.40.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", default-features = true, features = ["env-filter"] } chrono = "0.4" diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 5cd00e3..a8fbf4f 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -26,7 +26,11 @@ impl Default for Settings { Self { language: "en".to_string(), theme: "dark".to_string(), - display_targets: vec![hardware::HardwareType::CPU, hardware::HardwareType::Memory], + display_targets: vec![ + hardware::HardwareType::CPU, + hardware::HardwareType::Memory, + hardware::HardwareType::GPU, + ], } } } diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 0de30be..4bac46c 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -1,6 +1,5 @@ +use crate::services::graphic_service; use crate::{log_debug, log_error, log_internal, log_warn}; -use nvapi; -use nvapi::UtilizationDomain; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use std::thread; @@ -64,9 +63,11 @@ pub fn get_memory_usage(state: tauri::State<'_, AppState>) -> i32 { /// - return: `i32` GPU使用率(%) /// #[command] -pub fn get_gpu_usage(state: tauri::State<'_, AppState>) -> i32 { - let gpu_usage = state.gpu_usage.lock().unwrap(); - *gpu_usage as i32 +pub async fn get_gpu_usage() -> Result { + match graphic_service::get_nvidia_gpu_usage().await { + Ok(usage) => Ok((usage * 100.0).round() as i32), + Err(e) => Err(format!("Failed to get GPU usage: {:?}", e)), + } } /// @@ -187,56 +188,4 @@ pub fn initialize_system( thread::sleep(Duration::from_secs(SYSTEM_INFO_INIT_INTERVAL)); }); - - /// - /// TODO GPU使用率を取得する - /// - #[allow(dead_code)] - fn get_gpu_usage() -> Result { - log_debug!("start", "get_gpu_usage", None::<&str>); - - let gpus = nvapi::PhysicalGpu::enumerate()?; - - print!("{:?}", gpus); - - if gpus.is_empty() { - log_warn!("not found", "get_gpu_usage", Some("gpu is not found")); - tracing::warn!("gpu is not found"); - return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す - } - - let mut total_usage = 0.0; - let mut gpu_count = 0; - - for gpu in gpus.iter() { - let usage = match gpu.usages() { - Ok(usage) => usage, - Err(e) => { - log_error!("usages_failed", "get_gpu_usage", Some(e.to_string())); - return Err(e); - } - }; - - if let Some(gpu_usage) = usage.get(&UtilizationDomain::Graphics) { - let usage_f32 = gpu_usage.0 as f32 / 100.0; // Percentage を f32 に変換 - total_usage += usage_f32; - gpu_count += 1; - } - } - - if gpu_count == 0 { - log_warn!( - "no_usage", - "get_gpu_usage", - Some("No GPU usage data collected") - ); - return Err(nvapi::Status::Error); // 使用率が取得できなかった場合のエラーハンドリング - } - - let average_usage = total_usage / gpu_count as f32; - - log_debug!("end", "get_gpu_usage", None::<&str>); - - Ok(average_usage) - } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a646829..f0f6f98 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,60 +1,61 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -#[macro_use] - -mod commands; -mod enums; -mod utils; - -use commands::config; -use commands::hardware; - -use std::collections::VecDeque; -use std::sync::{Arc, Mutex}; -use sysinfo::System; - -fn main() { - utils::logger::init(); - - let app_state = config::AppState::new(); - - let system = Arc::new(Mutex::new(System::new_all())); - let cpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); - let memory_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); - let gpu_usage = Arc::new(Mutex::new(0.0)); - let gpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); - - let state = hardware::AppState { - system: Arc::clone(&system), - cpu_history: Arc::clone(&cpu_history), - memory_history: Arc::clone(&memory_history), - gpu_usage: Arc::clone(&gpu_usage), - gpu_history: Arc::clone(&gpu_history), - }; - - hardware::initialize_system( - system, - cpu_history, - memory_history, - gpu_usage, - gpu_history, - ); - - tauri::Builder::default() - .plugin(tauri_plugin_window_state::Builder::default().build()) - .manage(state) - .manage(app_state) - .invoke_handler(tauri::generate_handler![ - hardware::get_cpu_usage, - hardware::get_memory_usage, - hardware::get_gpu_usage, - hardware::get_cpu_usage_history, - hardware::get_memory_usage_history, - hardware::get_gpu_usage_history, - config::commands::set_language, - config::commands::set_theme, - config::commands::get_settings - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +#[macro_use] + +mod commands; +mod enums; +mod services; +mod utils; + +use commands::config; +use commands::hardware; + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; +use sysinfo::System; + +fn main() { + utils::logger::init(); + + let app_state = config::AppState::new(); + + let system = Arc::new(Mutex::new(System::new_all())); + let cpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); + let memory_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); + let gpu_usage = Arc::new(Mutex::new(0.0)); + let gpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); + + let state = hardware::AppState { + system: Arc::clone(&system), + cpu_history: Arc::clone(&cpu_history), + memory_history: Arc::clone(&memory_history), + gpu_usage: Arc::clone(&gpu_usage), + gpu_history: Arc::clone(&gpu_history), + }; + + hardware::initialize_system( + system, + cpu_history, + memory_history, + gpu_usage, + gpu_history, + ); + + tauri::Builder::default() + .plugin(tauri_plugin_window_state::Builder::default().build()) + .manage(state) + .manage(app_state) + .invoke_handler(tauri::generate_handler![ + hardware::get_cpu_usage, + hardware::get_memory_usage, + hardware::get_gpu_usage, + hardware::get_cpu_usage_history, + hardware::get_memory_usage_history, + hardware::get_gpu_usage_history, + config::commands::set_language, + config::commands::set_theme, + config::commands::get_settings + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs new file mode 100644 index 0000000..a33ab86 --- /dev/null +++ b/src-tauri/src/services/graphic_service.rs @@ -0,0 +1,71 @@ +use crate::{log_debug, log_error, log_info, log_internal, log_warn}; +use nvapi; +use nvapi::UtilizationDomain; +use tokio::task::spawn_blocking; +use tokio::task::JoinError; + +/// +/// GPU使用率を取得する(NVAPI を使用) +/// +pub async fn get_nvidia_gpu_usage() -> Result { + let handle = spawn_blocking(|| { + log_debug!("start", "get_nvidia_gpu_usage", None::<&str>); + + let gpus = nvapi::PhysicalGpu::enumerate()?; + + if gpus.is_empty() { + log_warn!( + "not found", + "get_nvidia_gpu_usage", + Some("gpu is not found") + ); + tracing::warn!("gpu is not found"); + return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す + } + + let mut total_usage = 0.0; + let mut gpu_count = 0; + + for gpu in gpus.iter() { + let usage = match gpu.usages() { + Ok(usage) => usage, + Err(e) => { + log_error!("usages_failed", "get_nvidia_gpu_usage", Some(e.to_string())); + return Err(e); + } + }; + + if let Some(gpu_usage) = usage.get(&UtilizationDomain::Graphics) { + let usage_f32 = gpu_usage.0 as f32 / 100.0; // Percentage を f32 に変換 + total_usage += usage_f32; + gpu_count += 1; + } + } + + log_info!( + &format!("gpu_count: {:?}", gpu_count), + "get_nvidia_gpu_usage", + None::<&str> + ); + + if gpu_count == 0 { + log_warn!( + "no_usage", + "get_nvidia_gpu_usage", + Some("No GPU usage data collected") + ); + return Err(nvapi::Status::Error); // 使用率が取得できなかった場合のエラーハンドリング + } + + let average_usage = total_usage / gpu_count as f32; + + log_debug!("end", "get_nvidia_gpu_usage", None::<&str>); + + Ok(average_usage) + }); + + handle.await.map_err(|e: JoinError| { + log_error!("join_error", "get_nvidia_gpu_usage", Some(e.to_string())); + nvapi::Status::Error + })? +} diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs new file mode 100644 index 0000000..1ee1286 --- /dev/null +++ b/src-tauri/src/services/mod.rs @@ -0,0 +1 @@ +pub mod graphic_service; From 323afae86b5e99da5489e06986677d96fa67130f Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 12:58:29 +0900 Subject: [PATCH 05/43] update: add rust linter --- .github/workflows/ci.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 384e775..8f77728 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,12 +35,19 @@ jobs: - name: install Rust stable uses: dtolnay/rust-toolchain@stable + - name: install Rust tool + run: rustup component add clippy + - name: install frontend dependencies run: npm ci - name: check front end Lint run: npm run lint:ci + - name: check back end Lint + run: cargo clippy --all-targets --all-features + working-directory: ./src-tauri + - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d62d43575b046e19d5c93f90851cf6459ab6d50e Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 13:13:59 +0900 Subject: [PATCH 06/43] Revert "update: add rust linter" This reverts commit 323afae86b5e99da5489e06986677d96fa67130f. --- .github/workflows/ci.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f77728..384e775 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,19 +35,12 @@ jobs: - name: install Rust stable uses: dtolnay/rust-toolchain@stable - - name: install Rust tool - run: rustup component add clippy - - name: install frontend dependencies run: npm ci - name: check front end Lint run: npm run lint:ci - - name: check back end Lint - run: cargo clippy --all-targets --all-features - working-directory: ./src-tauri - - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 79911d57dcc7716ee406ed068c828e2b562ac1a5 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 16:15:34 +0900 Subject: [PATCH 07/43] =?UTF-8?q?refactor:=20=E3=83=87=E3=82=A3=E3=83=AC?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/{ => charts}/CustomLegend.tsx | 0 src/components/{ => charts}/LineChart.tsx | 0 src/template/Chart.tsx | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/components/{ => charts}/CustomLegend.tsx (100%) rename src/components/{ => charts}/LineChart.tsx (100%) diff --git a/src/components/CustomLegend.tsx b/src/components/charts/CustomLegend.tsx similarity index 100% rename from src/components/CustomLegend.tsx rename to src/components/charts/CustomLegend.tsx diff --git a/src/components/LineChart.tsx b/src/components/charts/LineChart.tsx similarity index 100% rename from src/components/LineChart.tsx rename to src/components/charts/LineChart.tsx diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index 8c731b5..7538412 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -4,7 +4,7 @@ import { memoryUsageHistoryAtom, } from "@/atom/chart"; import { settingsAtom } from "@/atom/main"; -import LineChart from "@/components/LineChart"; +import LineChart from "@/components/charts/LineChart"; import { chartConfig } from "@/consts/chart"; import { useUsageUpdater } from "@/hooks/useHardwareData"; From cd74041dd000f6141a0c5953935f1d7874377705 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 18:00:51 +0900 Subject: [PATCH 08/43] =?UTF-8?q?add:=20=E8=A8=AD=E5=AE=9A=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E7=94=A8=E3=81=AEUI=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components.json | 20 + package-lock.json | 852 +++++++++++++++++- package.json | 14 +- src-tauri/src/main.rs | 7 + src-tauri/src/services/mod.rs | 1 + src-tauri/src/services/window_menu_service.rs | 25 + src/App.tsx | 6 + src/atom/ui.ts | 5 + src/components/modals/SettingsSheet.tsx | 118 +++ src/components/ui/button.tsx | 56 ++ src/components/ui/input.tsx | 25 + src/components/ui/label.tsx | 24 + src/components/ui/sheet.tsx | 138 +++ src/components/ui/switch.tsx | 27 + src/hooks/useTauriEventListener.ts | 27 + src/index.css | 78 ++ src/lib/utils.ts | 6 + tailwind.config.js | 83 +- tsconfig.json | 6 +- 19 files changed, 1504 insertions(+), 14 deletions(-) create mode 100644 components.json create mode 100644 src-tauri/src/services/window_menu_service.rs create mode 100644 src/atom/ui.ts create mode 100644 src/components/modals/SettingsSheet.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/hooks/useTauriEventListener.ts create mode 100644 src/lib/utils.ts diff --git a/components.json b/components.json new file mode 100644 index 0000000..31514bf --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/package-lock.json b/package-lock.json index c5524ff..d177d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,23 @@ "version": "0.0.1", "dependencies": { "@phosphor-icons/react": "^2.1.7", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.0", "@tauri-apps/api": "^1", "chart.js": "^4.4.4", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "jotai": "^2.9.3", + "lucide-react": "^0.437.0", "react": "^18.3.1", "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", - "tailwind-variants": "^0.2.1" + "tailwind-merge": "^2.5.2", + "tailwind-variants": "^0.2.1", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@biomejs/biome": "1.8.3", @@ -25,7 +35,7 @@ "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.20", - "postcss": "^8.4.41", + "postcss": "^8.4.42", "tailwindcss": "^3.4.10", "typescript": "^5.2.2", "vite": "^5.3.1", @@ -915,6 +925,44 @@ "node": ">=12" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", + "integrity": "sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.10", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.10.tgz", + "integrity": "sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.7" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.7.tgz", + "integrity": "sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==", + "license": "MIT" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1044,6 +1092,595 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.0.tgz", + "integrity": "sha512-OBzy5WAj641k0AOSpKQtreDMe+isX0MQJ1IVyF03ucdF3DunOnROVrjWs8zsXUxC3zfZ6JL9HFVCUlMghz9dJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.0.tgz", @@ -1566,7 +2203,7 @@ "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/react": "*" @@ -1729,6 +2366,18 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1979,6 +2628,36 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2073,6 +2752,12 @@ "node": ">=6" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2319,6 +3004,15 @@ "node": "*" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -2406,6 +3100,15 @@ "node": ">=16.17.0" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2615,6 +3318,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.437.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.437.0.tgz", + "integrity": "sha512-RXQq6tnm1FlXDUtOwLaoXET2TOEGpQULrQlPOjGHgIVsPhicHNat9sWF33OAe2UCLMFiWF1oL+FtAg43BqVY4Q==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -2913,9 +3625,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.42", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.42.tgz", + "integrity": "sha512-hywKUQB9Ra4dR1mGhldy5Aj1X3MWDSIA1cEi+Uy0CjheLvP6Ual5RlwMCh8i/X121yEDLDIKBsrCQ8ba3FDMfQ==", "funding": [ { "type": "opencollective", @@ -3132,6 +3844,76 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -3540,6 +4322,15 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3626,6 +4417,12 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -3678,6 +4475,49 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index c6a5506..c72d5f9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.20", - "postcss": "^8.4.41", + "postcss": "^8.4.42", "tailwindcss": "^3.4.10", "typescript": "^5.2.2", "vite": "^5.3.1", @@ -29,12 +29,22 @@ }, "dependencies": { "@phosphor-icons/react": "^2.1.7", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.0", "@tauri-apps/api": "^1", "chart.js": "^4.4.4", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "jotai": "^2.9.3", + "lucide-react": "^0.437.0", "react": "^18.3.1", "react-chartjs-2": "^5.2.0", "react-dom": "^18.3.1", - "tailwind-variants": "^0.2.1" + "tailwind-merge": "^2.5.2", + "tailwind-variants": "^0.2.1", + "tailwindcss-animate": "^1.0.7" } } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f0f6f98..dd2a977 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -9,6 +9,7 @@ mod utils; use commands::config; use commands::hardware; +use services::window_menu_service; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; @@ -33,6 +34,8 @@ fn main() { gpu_history: Arc::clone(&gpu_history), }; + let menu = window_menu_service::create_setting(); + hardware::initialize_system( system, cpu_history, @@ -45,6 +48,7 @@ fn main() { .plugin(tauri_plugin_window_state::Builder::default().build()) .manage(state) .manage(app_state) + .menu(menu) .invoke_handler(tauri::generate_handler![ hardware::get_cpu_usage, hardware::get_memory_usage, @@ -56,6 +60,9 @@ fn main() { config::commands::set_theme, config::commands::get_settings ]) + .on_menu_event(|event| { + window_menu_service::handle_menu_event(event); + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 1ee1286..5df5c39 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1 +1,2 @@ pub mod graphic_service; +pub mod window_menu_service; diff --git a/src-tauri/src/services/window_menu_service.rs b/src-tauri/src/services/window_menu_service.rs new file mode 100644 index 0000000..4cba4b4 --- /dev/null +++ b/src-tauri/src/services/window_menu_service.rs @@ -0,0 +1,25 @@ +use crate::{log_debug, log_error, log_info, log_internal, log_warn}; +use tauri::{CustomMenuItem, Menu, MenuItem, Submenu, WindowMenuEvent}; + +pub fn create_setting() -> Menu { + let settings = CustomMenuItem::new("preference".to_string(), "Preference"); + + let submenu = Submenu::new( + "File", + Menu::new().add_item(settings).add_native_item(MenuItem::Quit), + ); + + Menu::new().add_submenu(submenu) +} + +pub fn handle_menu_event(event: WindowMenuEvent) { + match event.menu_item_id() { + "preference" => { + log_info!("preference", "preference", None::<&str>); + + let window = event.window(); + window.emit("open_settings", {}).unwrap(); + } + _ => {} + } +} diff --git a/src/App.tsx b/src/App.tsx index 930dd13..f887e27 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,11 @@ import { useEffect, useState } from "react"; import TestTemplate from "./components/Sample"; import ChartTemplate from "./template/Chart"; import "./index.css"; +import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; import { useAtom } from "jotai"; import { settingsAtom } from "./atom/main"; + +import SettingsSheet from "./components/modals/SettingsSheet"; import { useDarkMode } from "./hooks/useDarkMode"; import { getSettings } from "./services/settingService"; @@ -26,6 +29,8 @@ const Page = () => { const [settings] = useAtom(settingsAtom); const { toggle } = useDarkMode(); + useSettingsModalListener(); + useLoadSettings(); const handleShowData = () => { @@ -47,6 +52,7 @@ const Page = () => { {buttonState === "raw" ? : } + ); }; diff --git a/src/atom/ui.ts b/src/atom/ui.ts new file mode 100644 index 0000000..2475c96 --- /dev/null +++ b/src/atom/ui.ts @@ -0,0 +1,5 @@ +import { atom } from "jotai"; + +export const modalAtoms = { + showSettingsModal: atom(false), +}; diff --git a/src/components/modals/SettingsSheet.tsx b/src/components/modals/SettingsSheet.tsx new file mode 100644 index 0000000..27754db --- /dev/null +++ b/src/components/modals/SettingsSheet.tsx @@ -0,0 +1,118 @@ +import { settingsAtom } from "@/atom/main"; +import { modalAtoms } from "@/atom/ui"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet"; +import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; +import type { ChartDataType } from "@/types/chartType"; +import { useAtom } from "jotai"; + +const SettingGraphType = () => { + const [settings] = useAtom(settingsAtom); + const selectedGraphTypes = settings.display_targets; + + const toggleGraphType = (type: ChartDataType) => { + console.log(type); + }; + + return ( +
+ +
+ + + +
+
+ ); +}; + +const SettingColorMode = () => { + const [settings] = useAtom(settingsAtom); + + const toggleDarkMode = (mode: "light" | "dark") => { + console.log(mode); + }; + + return ( +
+ +
+ + +
+
+ ); +}; + +const SettingsSheet = () => { + const [showSettingsModal] = useAtom(modalAtoms.showSettingsModal); + const { closeModal } = useSettingsModalListener(); + + return ( + + + + Edit Preference + + Make changes to your preferences here. Click save when you're done. + + +
+ + +
+ + + + + +
+
+ ); +}; + +export default SettingsSheet; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..a2aba9f --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", + { + variants: { + variant: { + default: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", + destructive: + "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", + outline: + "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + secondary: + "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..8f19870 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ) + } +) +Input.displayName = "Input" + +export { Input } diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..683faa7 --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +) + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)) +Label.displayName = LabelPrimitive.Root.displayName + +export { Label } diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx new file mode 100644 index 0000000..b07dd9e --- /dev/null +++ b/src/components/ui/sheet.tsx @@ -0,0 +1,138 @@ +import * as React from "react" +import * as SheetPrimitive from "@radix-ui/react-dialog" +import { cva, type VariantProps } from "class-variance-authority" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Sheet = SheetPrimitive.Root + +const SheetTrigger = SheetPrimitive.Trigger + +const SheetClose = SheetPrimitive.Close + +const SheetPortal = SheetPrimitive.Portal + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + } +) + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +SheetContent.displayName = SheetPrimitive.Content.displayName + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetHeader.displayName = "SheetHeader" + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +SheetFooter.displayName = "SheetFooter" + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetTitle.displayName = SheetPrimitive.Title.displayName + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SheetDescription.displayName = SheetPrimitive.Description.displayName + +export { + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +} diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx new file mode 100644 index 0000000..732e768 --- /dev/null +++ b/src/components/ui/switch.tsx @@ -0,0 +1,27 @@ +import * as React from "react" +import * as SwitchPrimitives from "@radix-ui/react-switch" + +import { cn } from "@/lib/utils" + +const Switch = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +Switch.displayName = SwitchPrimitives.Root.displayName + +export { Switch } diff --git a/src/hooks/useTauriEventListener.ts b/src/hooks/useTauriEventListener.ts new file mode 100644 index 0000000..4727c3a --- /dev/null +++ b/src/hooks/useTauriEventListener.ts @@ -0,0 +1,27 @@ +import { listen } from "@tauri-apps/api/event"; +import { useSetAtom } from "jotai"; +import { useEffect } from "react"; +import { modalAtoms } from "../atom/ui"; + +const useTauriEventListener = (event: string, callback: () => void) => { + useEffect(() => { + const unListen = listen(event, callback); + + return () => { + unListen.then((unListen) => unListen()); + }; + }, [event, callback]); +}; + +export const useSettingsModalListener = () => { + const setShowSettingsModal = useSetAtom(modalAtoms.showSettingsModal); + + // モーダルを開くイベントをリッスン + useTauriEventListener("open_settings", () => { + setShowSettingsModal(true); + }); + + const closeModal = () => setShowSettingsModal(false); + + return { closeModal }; +}; diff --git a/src/index.css b/src/index.css index b5c61c9..95d8cab 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,81 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 47.4% 11.2%; + + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + + --popover: 0 0% 100%; + --popover-foreground: 222.2 47.4% 11.2%; + + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + + --card: 0 0% 100%; + --card-foreground: 222.2 47.4% 11.2%; + + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + + --destructive: 0 100% 50%; + --destructive-foreground: 210 40% 98%; + + --ring: 215 20.2% 65.1%; + + --radius: 0.5rem; + } + + .dark { + --background: 224 71% 4%; + --foreground: 213 31% 91%; + + --muted: 223 47% 11%; + --muted-foreground: 215.4 16.3% 56.9%; + + --accent: 216 34% 17%; + --accent-foreground: 210 40% 98%; + + --popover: 224 71% 4%; + --popover-foreground: 215 20.2% 65.1%; + + --border: 216 34% 17%; + --input: 216 34% 17%; + + --card: 224 71% 4%; + --card-foreground: 213 31% 91%; + + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 1.2%; + + --secondary: 222.2 47.4% 11.2%; + --secondary-foreground: 210 40% 98%; + + --destructive: 0 63% 31%; + --destructive-foreground: 210 40% 98%; + + --ring: 216 34% 17%; + + --radius: 0.5rem; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..ac680b3 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/tailwind.config.js b/tailwind.config.js index e871354..8a7b6f9 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,9 +1,84 @@ +const { fontFamily } = require("tailwindcss/defaultTheme"); + /** @type {import('tailwindcss').Config} */ -export default { +module.exports = { + darkMode: ["class"], content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { - extend: {}, + container: { + center: "true", + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + fontFamily: { + sans: ["var(--font-sans)", ...fontFamily.sans], + }, + keyframes: { + "accordion-down": { + from: { + height: "0", + }, + to: { + height: "var(--radix-accordion-content-height)", + }, + }, + "accordion-up": { + from: { + height: "var(--radix-accordion-content-height)", + }, + to: { + height: "0", + }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, }, - plugins: [], - darkMode: "class", + plugins: [require("tailwindcss-animate")], }; diff --git a/tsconfig.json b/tsconfig.json index 5f9a283..7814c8a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,9 +19,11 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": "./src", + "baseUrl": ".", "paths": { - "@/*": ["*"] + "@/*": [ + "./src/*" + ] } }, "include": ["src"], From 3aa887c8cf83bc19c01e1903e1c9480c0343f9b1 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 18:06:32 +0900 Subject: [PATCH 09/43] refactor: run `npm run format` --- src/components/ui/button.tsx | 96 +++++++-------- src/components/ui/input.tsx | 38 +++--- src/components/ui/label.tsx | 34 +++--- src/components/ui/sheet.tsx | 221 ++++++++++++++++++----------------- src/components/ui/switch.tsx | 44 +++---- 5 files changed, 219 insertions(+), 214 deletions(-) diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index a2aba9f..93f0e28 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,56 +1,58 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", - { - variants: { - variant: { - default: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", - destructive: - "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", - outline: - "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - secondary: - "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", - ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", + { + variants: { + variant: { + default: + "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", + destructive: + "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", + outline: + "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + secondary: + "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + ghost: + "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } -) -Button.displayName = "Button" + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; -export { Button, buttonVariants } +export { Button, buttonVariants }; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 8f19870..d76fc7c 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -1,25 +1,25 @@ -import * as React from "react" +import * as React from "react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; export interface InputProps - extends React.InputHTMLAttributes {} + extends React.InputHTMLAttributes {} const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ) - } -) -Input.displayName = "Input" + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; -export { Input } +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 683faa7..82a8f37 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -1,24 +1,24 @@ -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { cva, type VariantProps } from "class-variance-authority" +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" -) + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps >(({ className, ...props }, ref) => ( - -)) -Label.displayName = LabelPrimitive.Root.displayName + +)); +Label.displayName = LabelPrimitive.Root.displayName; -export { Label } +export { Label }; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index b07dd9e..f76b165 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -1,138 +1,141 @@ -import * as React from "react" -import * as SheetPrimitive from "@radix-ui/react-dialog" -import { cva, type VariantProps } from "class-variance-authority" -import { X } from "lucide-react" +import * as React from "react"; +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { cva, type VariantProps } from "class-variance-authority"; +import { X } from "lucide-react"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const Sheet = SheetPrimitive.Root +const Sheet = SheetPrimitive.Root; -const SheetTrigger = SheetPrimitive.Trigger +const SheetTrigger = SheetPrimitive.Trigger; -const SheetClose = SheetPrimitive.Close +const SheetClose = SheetPrimitive.Close; -const SheetPortal = SheetPrimitive.Portal +const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SheetOverlay.displayName = SheetPrimitive.Overlay.displayName + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950", - { - variants: { - side: { - top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", - bottom: - "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", - left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", - right: - "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", - }, - }, - defaultVariants: { - side: "right", - }, - } -) + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + }, +); interface SheetContentProps - extends React.ComponentPropsWithoutRef, - VariantProps {} + extends React.ComponentPropsWithoutRef, + VariantProps {} const SheetContent = React.forwardRef< - React.ElementRef, - SheetContentProps + React.ElementRef, + SheetContentProps >(({ side = "right", className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)) -SheetContent.displayName = SheetPrimitive.Content.displayName + + + + {children} + + + Close + + + +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
-) -SheetHeader.displayName = "SheetHeader" +
+); +SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
-) -SheetFooter.displayName = "SheetFooter" +
+); +SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SheetTitle.displayName = SheetPrimitive.Title.displayName + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - -)) -SheetDescription.displayName = SheetPrimitive.Description.displayName + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; export { - Sheet, - SheetPortal, - SheetOverlay, - SheetTrigger, - SheetClose, - SheetContent, - SheetHeader, - SheetFooter, - SheetTitle, - SheetDescription, -} + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, +}; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index 732e768..4ee4cf9 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -1,27 +1,27 @@ -import * as React from "react" -import * as SwitchPrimitives from "@radix-ui/react-switch" +import * as React from "react"; +import * as SwitchPrimitives from "@radix-ui/react-switch"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - -)) -Switch.displayName = SwitchPrimitives.Root.displayName + + + +)); +Switch.displayName = SwitchPrimitives.Root.displayName; -export { Switch } +export { Switch }; From fdd0b0c4f3e6844752aa30369b753838d3a368cd Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 1 Sep 2024 18:20:34 +0900 Subject: [PATCH 10/43] refactor: format --- src/components/ui/button.tsx | 4 ++-- src/components/ui/label.tsx | 4 ++-- src/components/ui/sheet.tsx | 4 ++-- src/components/ui/switch.tsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 93f0e28..aa4de5e 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; import { cn } from "@/lib/utils"; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 82a8f37..6e126e9 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -1,6 +1,6 @@ -import * as React from "react"; import * as LabelPrimitive from "@radix-ui/react-label"; -import { cva, type VariantProps } from "class-variance-authority"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; import { cn } from "@/lib/utils"; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index f76b165..ff823cc 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -1,7 +1,7 @@ -import * as React from "react"; import * as SheetPrimitive from "@radix-ui/react-dialog"; -import { cva, type VariantProps } from "class-variance-authority"; +import { type VariantProps, cva } from "class-variance-authority"; import { X } from "lucide-react"; +import * as React from "react"; import { cn } from "@/lib/utils"; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index 4ee4cf9..a190d40 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -1,5 +1,5 @@ -import * as React from "react"; import * as SwitchPrimitives from "@radix-ui/react-switch"; +import * as React from "react"; import { cn } from "@/lib/utils"; From afe03fe8734909290250a55bc84111844082d2af Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 2 Sep 2024 01:59:51 +0900 Subject: [PATCH 11/43] add: setting --- src-tauri/src/commands/config.rs | 16 +++++++ src-tauri/src/enums/hardware.rs | 22 +++++++++- src-tauri/src/main.rs | 1 + src/App.tsx | 22 ++-------- src/atom/main.ts | 8 ---- src/atom/useSettingsAtom.ts | 42 +++++++++++++++++++ src/services/settingService.ts | 10 +++++ src/template/Chart.tsx | 4 +- .../modals => template}/SettingsSheet.tsx | 27 +++++------- 9 files changed, 104 insertions(+), 48 deletions(-) delete mode 100644 src/atom/main.ts create mode 100644 src/atom/useSettingsAtom.ts rename src/{components/modals => template}/SettingsSheet.tsx (82%) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index a8fbf4f..dbf8940 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -77,6 +77,12 @@ impl Settings { self.write_file(); println!("{:?}", self); } + + pub fn set_display_targets(&mut self, new_targets: Vec) { + self.display_targets = new_targets; + self.write_file(); + println!("{:?}", self); + } } #[derive(Debug)] @@ -122,4 +128,14 @@ pub mod commands { let settings = state.settings.lock().unwrap().clone(); Ok(settings) } + + #[tauri::command] + pub async fn set_display_targets( + state: tauri::State<'_, AppState>, + new_targets: Vec, + ) -> Result<(), String> { + let mut settings = state.settings.lock().unwrap(); + settings.set_display_targets(new_targets); + Ok(()) + } } diff --git a/src-tauri/src/enums/hardware.rs b/src-tauri/src/enums/hardware.rs index 2fb1c34..3b76c97 100644 --- a/src-tauri/src/enums/hardware.rs +++ b/src-tauri/src/enums/hardware.rs @@ -1,6 +1,6 @@ -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; -#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum HardwareType { CPU, Memory, @@ -20,3 +20,21 @@ impl Serialize for HardwareType { serializer.serialize_str(s) } } + +impl<'de> Deserialize<'de> for HardwareType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?.to_lowercase(); + match s.as_str() { + "cpu" => Ok(HardwareType::CPU), + "memory" => Ok(HardwareType::Memory), + "gpu" => Ok(HardwareType::GPU), + _ => Err(serde::de::Error::unknown_variant( + &s, + &["cpu", "memory", "gpu"], + )), + } + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index dd2a977..3e5e319 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -58,6 +58,7 @@ fn main() { hardware::get_gpu_usage_history, config::commands::set_language, config::commands::set_theme, + config::commands::set_display_targets, config::commands::get_settings ]) .on_menu_event(|event| { diff --git a/src/App.tsx b/src/App.tsx index f887e27..9d1b8c3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,36 +3,20 @@ import TestTemplate from "./components/Sample"; import ChartTemplate from "./template/Chart"; import "./index.css"; import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; -import { useAtom } from "jotai"; -import { settingsAtom } from "./atom/main"; +import { useSettingsAtom } from "./atom/useSettingsAtom"; -import SettingsSheet from "./components/modals/SettingsSheet"; +import SettingsSheet from "@/template/SettingsSheet"; import { useDarkMode } from "./hooks/useDarkMode"; -import { getSettings } from "./services/settingService"; type ButtonState = "chart" | "raw"; -const useLoadSettings = () => { - const [, setSettings] = useAtom(settingsAtom); - - useEffect(() => { - const loadSettings = async () => { - const setting = await getSettings(); - setSettings(setting); - }; - loadSettings(); - }, [setSettings]); -}; - const Page = () => { const [buttonState, setButtonState] = useState("chart"); - const [settings] = useAtom(settingsAtom); + const { settings } = useSettingsAtom(); const { toggle } = useDarkMode(); useSettingsModalListener(); - useLoadSettings(); - const handleShowData = () => { setButtonState(buttonState === "raw" ? "chart" : "raw"); }; diff --git a/src/atom/main.ts b/src/atom/main.ts deleted file mode 100644 index a7ef9c5..0000000 --- a/src/atom/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Settings } from "@/types/settingsType"; -import { atom } from "jotai"; - -export const settingsAtom = atom({ - language: "en", - theme: "light", - display_targets: [], -}); diff --git a/src/atom/useSettingsAtom.ts b/src/atom/useSettingsAtom.ts new file mode 100644 index 0000000..496fb31 --- /dev/null +++ b/src/atom/useSettingsAtom.ts @@ -0,0 +1,42 @@ +import { + getSettings, + setDisplayTargets, + setTheme, +} from "@/services/settingService"; +import type { ChartDataType } from "@/types/chartType"; +import type { Settings } from "@/types/settingsType"; +import { atom, useAtom } from "jotai"; +import { useEffect } from "react"; +const settingsAtom = atom({ + language: "en", + theme: "light", + display_targets: [], +}); + +export const useSettingsAtom = () => { + const [settings, setSettings] = useAtom(settingsAtom); + + useEffect(() => { + const loadSettings = async () => { + const setting = await getSettings(); + setSettings(setting); + }; + loadSettings(); + }, [setSettings]); + + const toggleDisplayTarget = async (target: ChartDataType) => { + const newTargets = settings.display_targets.includes(target) + ? settings.display_targets.filter((t) => t !== target) + : [...settings.display_targets, target]; + + setSettings((prev) => ({ ...prev, display_targets: newTargets })); + await setDisplayTargets(settings.display_targets); + }; + + const toggleTheme = async (theme: "light" | "dark") => { + setSettings((prev) => ({ ...prev, theme })); + await setTheme(theme); + }; + + return { settings, toggleDisplayTarget, toggleTheme }; +}; diff --git a/src/services/settingService.ts b/src/services/settingService.ts index d9dfd59..d56b2e6 100644 --- a/src/services/settingService.ts +++ b/src/services/settingService.ts @@ -4,3 +4,13 @@ import { invoke } from "@tauri-apps/api/tauri"; export const getSettings = async (): Promise => { return await invoke("get_settings"); }; + +export const setTheme = async (theme: Settings["theme"]): Promise => { + return await invoke("set_theme", { newTheme: theme }); +}; + +export const setDisplayTargets = async ( + targets: Settings["display_targets"], +): Promise => { + return await invoke("set_display_targets", { newTargets: targets }); +}; diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index 7538412..1c4e866 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -3,7 +3,7 @@ import { graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; -import { settingsAtom } from "@/atom/main"; +import { useSettingsAtom } from "@/atom/useSettingsAtom"; import LineChart from "@/components/charts/LineChart"; import { chartConfig } from "@/consts/chart"; import { useUsageUpdater } from "@/hooks/useHardwareData"; @@ -45,7 +45,7 @@ const GpuUsageChart = () => { }; const ChartTemplate = () => { - const [settings] = useAtom(settingsAtom); + const { settings } = useSettingsAtom(); const renderedCharts = useMemo(() => { return ( diff --git a/src/components/modals/SettingsSheet.tsx b/src/template/SettingsSheet.tsx similarity index 82% rename from src/components/modals/SettingsSheet.tsx rename to src/template/SettingsSheet.tsx index 27754db..b2861ee 100644 --- a/src/components/modals/SettingsSheet.tsx +++ b/src/template/SettingsSheet.tsx @@ -1,13 +1,11 @@ -import { settingsAtom } from "@/atom/main"; import { modalAtoms } from "@/atom/ui"; +import { useSettingsAtom } from "@/atom/useSettingsAtom"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Sheet, - SheetClose, SheetContent, SheetDescription, - SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; @@ -16,11 +14,11 @@ import type { ChartDataType } from "@/types/chartType"; import { useAtom } from "jotai"; const SettingGraphType = () => { - const [settings] = useAtom(settingsAtom); + const { settings, toggleDisplayTarget } = useSettingsAtom(); const selectedGraphTypes = settings.display_targets; - const toggleGraphType = (type: ChartDataType) => { - console.log(type); + const toggleGraphType = async (type: ChartDataType) => { + await toggleDisplayTarget(type); }; return ( @@ -35,7 +33,7 @@ const SettingGraphType = () => { checked={selectedGraphTypes.includes("cpu")} onChange={() => toggleGraphType("cpu")} /> - Line Graph + CPU
@@ -59,10 +57,10 @@ const SettingGraphType = () => { }; const SettingColorMode = () => { - const [settings] = useAtom(settingsAtom); + const { settings, toggleTheme } = useSettingsAtom(); - const toggleDarkMode = (mode: "light" | "dark") => { - console.log(mode); + const toggleDarkMode = async (mode: "light" | "dark") => { + await toggleTheme(mode); }; return ( @@ -105,11 +103,6 @@ const SettingsSheet = () => {
- - - - - ); From b5e770e9dd16f50856d3b863e8dc3668a58708a8 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 8 Sep 2024 19:33:13 +0900 Subject: [PATCH 12/43] =?UTF-8?q?update:=20=E8=A8=AD=E5=AE=9A=E8=AA=AD?= =?UTF-8?q?=E3=81=BF=E8=BE=BC=E3=81=BF=E3=83=BB=E6=9B=B4=E6=96=B0=E6=99=82?= =?UTF-8?q?=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 101 +++++++++++ src-tauri/Cargo.toml | 2 +- src-tauri/src/commands/config.rs | 157 ++++++++++++++---- src-tauri/src/services/window_menu_service.rs | 2 +- src-tauri/tauri.conf.json | 142 ++++++++-------- src/App.tsx | 6 +- src/atom/useSettingsAtom.ts | 17 +- src/hooks/useTauriEventListener.ts | 29 ++++ 8 files changed, 347 insertions(+), 109 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 2b8f2e0..1a1277c 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1736,6 +1736,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -2266,6 +2277,30 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2781,6 +2816,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "raw-window-handle", + "rfd", "semver", "serde", "serde_json", @@ -3377,6 +3413,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.93" @@ -3406,6 +3454,16 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3522,6 +3580,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -3782,6 +3853,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3800,6 +3877,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3824,6 +3907,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3842,6 +3931,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3872,6 +3967,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0324f1f..2c3173e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -29,7 +29,7 @@ tauri-build = { version = "1.5.2", features = [] } [dependencies] serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } -tauri = { version = "1.6.5", features = [] } +tauri = { version = "1.6.5", features = ["dialog-all"] } sysinfo = "0.31.3" tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } nvapi = "=0.1.4" diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index dbf8940..3e8c6d3 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -10,8 +10,8 @@ use std::sync::Mutex; const SETTINGS_FILENAME: &str = "settings.json"; trait Config { - fn write_file(&self) {} - fn read_file(&mut self) {} + fn write_file(&self) -> Result<(), String>; + fn read_file(&mut self) -> Result<(), String>; } #[derive(Debug, Serialize, Deserialize, Clone)] @@ -36,52 +36,102 @@ impl Default for Settings { } impl Config for Settings { - fn write_file(&self) { + fn write_file(&self) -> Result<(), String> { let config_file = get_app_data_dir(SETTINGS_FILENAME); if !config_file.parent().unwrap().exists() { fs::create_dir_all(config_file.parent().unwrap()).unwrap(); } - let serialized = serde_json::to_string(self).unwrap(); - let mut file = fs::File::create(config_file).unwrap(); - file.write_all(&serialized.as_bytes()).unwrap(); + + match serde_json::to_string(self) { + Ok(serialized) => { + if let Err(e) = fs::File::create(config_file) + .and_then(|mut file| file.write_all(&serialized.as_bytes())) + { + // [TODO] ログの定数化 + log_error!( + "Failed to serialize settings", + "write_file", + Some(e.to_string()) + ); + return Err(format!("Failed to write to settings file: {}", e)); + } + } + Err(e) => { + log_error!( + "Failed to serialize settings", + "write_file", + Some(e.to_string()) + ); + return Err(format!("Failed to serialize settings: {}", e)); + } + } + + Ok(()) } - fn read_file(&mut self) { + fn read_file(&mut self) -> Result<(), String> { let config_file = get_app_data_dir(SETTINGS_FILENAME); - let input = fs::read_to_string(config_file).unwrap(); - let deserialized: Self = serde_json::from_str(&input).unwrap(); - let _ = mem::replace(self, deserialized); + + match fs::read_to_string(config_file) { + Ok(input) => match serde_json::from_str::(&input) { + Ok(deserialized) => { + *self = deserialized; + Ok(()) + } + Err(e) => { + log_error!( + "Failed to deserialize settings", + "read_file", + Some(e.to_string()) + ); + Err(format!("Failed to deserialize settings: {}", e)) + } + }, + Err(e) => { + log_error!( + "Failed to deserialize settings", + "read_file", + Some(e.to_string()) + ); + Err(format!("Failed to read settings file: {}", e)) + } + } } } impl Settings { pub fn new() -> Self { let config_file = get_app_data_dir(SETTINGS_FILENAME); + + let mut settings = Self::default(); + if !config_file.exists() { - Self::default() - } else { - let mut settings = Self::default(); - settings.read_file(); - settings + return settings; } + + if let Err(e) = settings.read_file() { + log_error!("read_config_failed", "read_file", Some(e.to_string())); + } + + settings } - pub fn set_language(&mut self, new_lang: String) { + pub fn set_language(&mut self, new_lang: String) -> Result<(), String> { self.language = new_lang; - self.write_file(); - println!("{:?}", self); + self.write_file() } - pub fn set_theme(&mut self, new_theme: String) { + pub fn set_theme(&mut self, new_theme: String) -> Result<(), String> { self.theme = new_theme; - self.write_file(); - println!("{:?}", self); + self.write_file() } - pub fn set_display_targets(&mut self, new_targets: Vec) { + pub fn set_display_targets( + &mut self, + new_targets: Vec, + ) -> Result<(), String> { self.display_targets = new_targets; - self.write_file(); - println!("{:?}", self); + self.write_file() } } @@ -100,42 +150,79 @@ impl AppState { pub mod commands { use super::*; + use serde_json::json; + use tauri::Window; + + const ERROR_TITLE: &str = "設定の更新に失敗しました"; + const ERROR_MESSAGE: &str = "何度も発生する場合は settings.json を削除してください"; + + fn emit_error(window: &Window) -> Result<(), String> { + window + .emit( + "error_event", + json!({ + "title": ERROR_TITLE, + "message": ERROR_MESSAGE + }), + ) + .map_err(|e| format!("Failed to emit event: {}", e))?; + + Ok(()) + } #[tauri::command] + pub async fn get_settings( + state: tauri::State<'_, AppState>, + ) -> Result { + let settings = state.settings.lock().unwrap().clone(); + Ok(settings) + } + + #[tauri::command] + pub async fn set_language( + window: Window, state: tauri::State<'_, AppState>, new_language: String, ) -> Result<(), String> { let mut settings = state.settings.lock().unwrap(); - settings.set_language(new_language); + + if let Err(e) = settings.set_language(new_language) { + emit_error(&window)?; + return Err(e); + } + Ok(()) } #[tauri::command] pub async fn set_theme( + window: Window, state: tauri::State<'_, AppState>, new_theme: String, ) -> Result<(), String> { let mut settings = state.settings.lock().unwrap(); - settings.set_theme(new_theme); - Ok(()) - } - #[tauri::command] - pub async fn get_settings( - state: tauri::State<'_, AppState>, - ) -> Result { - let settings = state.settings.lock().unwrap().clone(); - Ok(settings) + if let Err(e) = settings.set_theme(new_theme) { + emit_error(&window)?; + return Err(e); + } + + Ok(()) } #[tauri::command] pub async fn set_display_targets( + window: Window, state: tauri::State<'_, AppState>, new_targets: Vec, ) -> Result<(), String> { let mut settings = state.settings.lock().unwrap(); - settings.set_display_targets(new_targets); + + if let Err(e) = settings.set_display_targets(new_targets) { + emit_error(&window)?; + return Err(e); + } Ok(()) } } diff --git a/src-tauri/src/services/window_menu_service.rs b/src-tauri/src/services/window_menu_service.rs index 4cba4b4..8725476 100644 --- a/src-tauri/src/services/window_menu_service.rs +++ b/src-tauri/src/services/window_menu_service.rs @@ -15,7 +15,7 @@ pub fn create_setting() -> Menu { pub fn handle_menu_event(event: WindowMenuEvent) { match event.menu_item_id() { "preference" => { - log_info!("preference", "preference", None::<&str>); + log_debug!("preference", "preference", None::<&str>); let window = event.window(); window.emit("open_settings", {}).unwrap(); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e6d9789..601f116 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,67 +1,75 @@ -{ - "$schema": "../node_modules/@tauri-apps/cli/schema.json", - "build": { - "beforeBuildCommand": "npm run build", - "beforeDevCommand": "npm run dev", - "devPath": "http://localhost:1520", - "distDir": "../dist" - }, - "package": { - "productName": "hardware-monitor", - "version": "0.1.0" - }, - "tauri": { - "allowlist": { - "all": false - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "deb": { - "depends": [] - }, - "externalBin": [], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "identifier": "shm11C3.HardwareMonitor", - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null - }, - "resources": [], - "shortDescription": "", - "targets": "all", - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "", - "webviewInstallMode": { - "type": "downloadBootstrapper" - } - } - }, - "security": { - "csp": null - }, - "updater": { - "active": false - }, - "windows": [ - { - "resizable": true, - "title": "hardware-monitor", - "decorations": true - } - ] - } -} +{ + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "build": { + "beforeBuildCommand": "npm run build", + "beforeDevCommand": "npm run dev", + "devPath": "http://localhost:1520", + "distDir": "../dist" + }, + "package": { + "productName": "hardware-monitor", + "version": "0.1.0" + }, + "tauri": { + "allowlist": { + "all": false, + "dialog": { + "all": true, + "ask": true, + "confirm": true, + "message": true, + "open": true, + "save": true + } + }, + "bundle": { + "active": true, + "category": "DeveloperTool", + "copyright": "", + "deb": { + "depends": [] + }, + "externalBin": [], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "identifier": "shm11C3.HardwareMonitor", + "longDescription": "", + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "resources": [], + "shortDescription": "", + "targets": "all", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "", + "webviewInstallMode": { + "type": "downloadBootstrapper" + } + } + }, + "security": { + "csp": null + }, + "updater": { + "active": false + }, + "windows": [ + { + "resizable": true, + "title": "hardware-monitor", + "decorations": true + } + ] + } +} diff --git a/src/App.tsx b/src/App.tsx index 9d1b8c3..26febd0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,10 @@ import { useEffect, useState } from "react"; import TestTemplate from "./components/Sample"; import ChartTemplate from "./template/Chart"; import "./index.css"; -import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; +import { + useErrorModalListener, + useSettingsModalListener, +} from "@/hooks/useTauriEventListener"; import { useSettingsAtom } from "./atom/useSettingsAtom"; import SettingsSheet from "@/template/SettingsSheet"; @@ -16,6 +19,7 @@ const Page = () => { const { toggle } = useDarkMode(); useSettingsModalListener(); + useErrorModalListener(); const handleShowData = () => { setButtonState(buttonState === "raw" ? "chart" : "raw"); diff --git a/src/atom/useSettingsAtom.ts b/src/atom/useSettingsAtom.ts index 496fb31..237591c 100644 --- a/src/atom/useSettingsAtom.ts +++ b/src/atom/useSettingsAtom.ts @@ -29,13 +29,22 @@ export const useSettingsAtom = () => { ? settings.display_targets.filter((t) => t !== target) : [...settings.display_targets, target]; - setSettings((prev) => ({ ...prev, display_targets: newTargets })); - await setDisplayTargets(settings.display_targets); + try { + // [TODO] Result型を作りたい + await setDisplayTargets(settings.display_targets); + setSettings((prev) => ({ ...prev, display_targets: newTargets })); + } catch (e) { + console.error(e); + } }; const toggleTheme = async (theme: "light" | "dark") => { - setSettings((prev) => ({ ...prev, theme })); - await setTheme(theme); + try { + await setTheme(theme); + setSettings((prev) => ({ ...prev, theme })); + } catch (e) { + console.error(e); + } }; return { settings, toggleDisplayTarget, toggleTheme }; diff --git a/src/hooks/useTauriEventListener.ts b/src/hooks/useTauriEventListener.ts index 4727c3a..b19f58b 100644 --- a/src/hooks/useTauriEventListener.ts +++ b/src/hooks/useTauriEventListener.ts @@ -1,3 +1,4 @@ +import { message } from "@tauri-apps/api/dialog"; import { listen } from "@tauri-apps/api/event"; import { useSetAtom } from "jotai"; import { useEffect } from "react"; @@ -13,6 +14,11 @@ const useTauriEventListener = (event: string, callback: () => void) => { }, [event, callback]); }; +/** + * モーダルを開くイベントをリッスンして、モーダルを表示する + * + * @returns closeModal + */ export const useSettingsModalListener = () => { const setShowSettingsModal = useSetAtom(modalAtoms.showSettingsModal); @@ -25,3 +31,26 @@ export const useSettingsModalListener = () => { return { closeModal }; }; + +/** + * バックエンド側のエラーイベントをリッスンして、エラーダイヤログを表示する + */ +export const useErrorModalListener = () => { + useEffect(() => { + const unListen = listen("error_event", (event) => { + const { title, message: errorMessage } = event.payload as { + title: string; + message: string; + }; + + message(errorMessage, { + title: title, + type: "error", + }); + }); + + return () => { + unListen.then((off) => off()); + }; + }, []); +}; From d77e07431c1220b23c705218c7472385a89af234 Mon Sep 17 00:00:00 2001 From: shm Date: Thu, 12 Sep 2024 01:50:30 +0900 Subject: [PATCH 13/43] change: `fill : true` --- src/components/charts/LineChart.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index dac664b..514c618 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -4,6 +4,7 @@ import { CategoryScale, Chart as ChartJS, type ChartOptions, + Filler, Legend, LineElement, LinearScale, @@ -24,6 +25,7 @@ ChartJS.register( Title, Tooltip, Legend, + Filler, ); const LineChart = ({ From 1098aedb7678f19c6e3e23d71ddbc2b94f587e98 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 14 Sep 2024 18:59:20 +0900 Subject: [PATCH 14/43] =?UTF-8?q?fix:=20=E8=A8=AD=E5=AE=9A=E3=81=8C?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=8D=E3=81=AA=E3=81=84=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom/useSettingsAtom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/atom/useSettingsAtom.ts b/src/atom/useSettingsAtom.ts index 237591c..ce0c6e6 100644 --- a/src/atom/useSettingsAtom.ts +++ b/src/atom/useSettingsAtom.ts @@ -31,7 +31,7 @@ export const useSettingsAtom = () => { try { // [TODO] Result型を作りたい - await setDisplayTargets(settings.display_targets); + await setDisplayTargets(newTargets); setSettings((prev) => ({ ...prev, display_targets: newTargets })); } catch (e) { console.error(e); From 4856abefa6912f1f0c3e6fd20d7af7a74fc11ce5 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 14 Sep 2024 19:03:16 +0900 Subject: [PATCH 15/43] refactor: rename: `chartType.ts` --- src/atom/useSettingsAtom.ts | 2 +- src/components/charts/LineChart.tsx | 2 +- src/hooks/useHardwareData.ts | 2 +- src/template/SettingsSheet.tsx | 2 +- src/types/{chartType.ts => hardwareDataType.ts} | 0 src/types/settingsType.ts | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename src/types/{chartType.ts => hardwareDataType.ts} (100%) diff --git a/src/atom/useSettingsAtom.ts b/src/atom/useSettingsAtom.ts index ce0c6e6..486b714 100644 --- a/src/atom/useSettingsAtom.ts +++ b/src/atom/useSettingsAtom.ts @@ -3,7 +3,7 @@ import { setDisplayTargets, setTheme, } from "@/services/settingService"; -import type { ChartDataType } from "@/types/chartType"; +import type { ChartDataType } from "@/types/hardwareDataType"; import type { Settings } from "@/types/settingsType"; import { atom, useAtom } from "jotai"; import { useEffect } from "react"; diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index 514c618..5c882c6 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -1,4 +1,4 @@ -import type { ChartDataType } from "@/types/chartType"; +import type { ChartDataType } from "@/types/hardwareDataType"; import { Cpu, GraphicsCard, Memory } from "@phosphor-icons/react"; import { CategoryScale, diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts index 066515f..9e91bbb 100644 --- a/src/hooks/useHardwareData.ts +++ b/src/hooks/useHardwareData.ts @@ -9,7 +9,7 @@ import { getGpuUsage, getMemoryUsage, } from "@/services/hardwareService"; -import type { ChartDataType } from "@/types/chartType"; +import type { ChartDataType } from "@/types/hardwareDataType"; import { type PrimitiveAtom, useSetAtom } from "jotai"; import { useEffect } from "react"; diff --git a/src/template/SettingsSheet.tsx b/src/template/SettingsSheet.tsx index b2861ee..65c0c2b 100644 --- a/src/template/SettingsSheet.tsx +++ b/src/template/SettingsSheet.tsx @@ -10,7 +10,7 @@ import { SheetTitle, } from "@/components/ui/sheet"; import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; -import type { ChartDataType } from "@/types/chartType"; +import type { ChartDataType } from "@/types/hardwareDataType"; import { useAtom } from "jotai"; const SettingGraphType = () => { diff --git a/src/types/chartType.ts b/src/types/hardwareDataType.ts similarity index 100% rename from src/types/chartType.ts rename to src/types/hardwareDataType.ts diff --git a/src/types/settingsType.ts b/src/types/settingsType.ts index b618b03..98e6dcd 100644 --- a/src/types/settingsType.ts +++ b/src/types/settingsType.ts @@ -1,4 +1,4 @@ -import type { ChartDataType } from "./chartType"; +import type { ChartDataType } from "./hardwareDataType"; export type Settings = { language: string; From 22db18712d04d9f7d3a8efb93c4d162bb0509166 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 02:14:02 +0900 Subject: [PATCH 16/43] add: get hardware info --- src-tauri/Cargo.lock | 46 ++++- src-tauri/Cargo.toml | 3 +- src-tauri/src/commands/hardware.rs | 34 +++ src-tauri/src/main.rs | 1 + src-tauri/src/services/mod.rs | 1 + src-tauri/src/services/system_info_service.rs | 193 ++++++++++++++++++ src-tauri/src/utils/mod.rs | 1 + src-tauri/src/utils/unit.rs | 16 ++ src/App.tsx | 5 +- src/atom/useHardwareInfoAtom.ts | 30 +++ src/services/hardwareService.ts | 5 + src/types/hardwareDataType.ts | 23 +++ 12 files changed, 353 insertions(+), 5 deletions(-) create mode 100644 src-tauri/src/services/system_info_service.rs create mode 100644 src-tauri/src/utils/unit.rs create mode 100644 src/atom/useHardwareInfoAtom.ts diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1a1277c..8fc9472 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -84,6 +84,7 @@ dependencies = [ "tracing", "tracing-subscriber", "windows 0.58.0", + "wmi", ] [[package]] @@ -794,6 +795,21 @@ dependencies = [ "new_debug_unreachable", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -801,6 +817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -837,6 +854,12 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "futures-task" version = "0.3.30" @@ -849,9 +872,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -2677,9 +2704,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.31.3" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b92e0bdf838cbc1c4c9ba14f9c97a7ec6cdcd1ae66b10e1e42775a25553f45d" +checksum = "355dbe4f8799b304b05e1b0f05fc59b2a18d36645cf169607da45bde2f69a1be" dependencies = [ "core-foundation-sys", "libc", @@ -4019,6 +4046,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wmi" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbdda506bdee26ba617bd814538b690e14f59e8185345344cff113a8be21c005" +dependencies = [ + "chrono", + "futures", + "log", + "serde", + "thiserror", + "windows 0.58.0", + "windows-core 0.58.0", +] + [[package]] name = "wry" version = "0.24.10" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2c3173e..e54d455 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -30,13 +30,14 @@ tauri-build = { version = "1.5.2", features = [] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } tauri = { version = "1.6.5", features = ["dialog-all"] } -sysinfo = "0.31.3" +sysinfo = "0.31.4" tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } nvapi = "=0.1.4" tokio = { version = "1.40.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", default-features = true, features = ["env-filter"] } chrono = "0.4" +wmi = "0.14" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 4bac46c..c3e8604 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -1,5 +1,7 @@ use crate::services::graphic_service; +use crate::services::system_info_service; use crate::{log_debug, log_error, log_internal, log_warn}; +use serde::Serialize; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use std::thread; @@ -41,6 +43,38 @@ pub fn get_cpu_usage(state: tauri::State<'_, AppState>) -> i32 { usage.round() as i32 } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SysInfo { + pub cpu: system_info_service::CpuInfo, + pub memory: system_info_service::MemoryInfo, + //pub gpu: GpuInfo, +} + +/// +/// ## システム情報を取得 +/// +#[command] +pub fn get_hardware_info(state: tauri::State<'_, AppState>) -> Result { + let cpu = system_info_service::get_cpu_info(state.system.lock().unwrap()); + let memory = system_info_service::get_memory_info(); + + match (cpu, memory) { + (Ok(cpu_info), Ok(memory_info)) => Ok(SysInfo { + cpu: cpu_info, + memory: memory_info, + }), + (Err(e), _) | (_, Err(e)) => { + log_error!( + "get_sys_info_failed", + "get_hardware_info", + Some(e.to_string()) + ); + Err(format!("Failed to get hardware info: {}", e)) + } + } +} + /// /// ## メモリ使用率(%)を取得 /// diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 3e5e319..53c7c2f 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -51,6 +51,7 @@ fn main() { .menu(menu) .invoke_handler(tauri::generate_handler![ hardware::get_cpu_usage, + hardware::get_hardware_info, hardware::get_memory_usage, hardware::get_gpu_usage, hardware::get_cpu_usage_history, diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 5df5c39..1218bac 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,2 +1,3 @@ pub mod graphic_service; +pub mod system_info_service; pub mod window_menu_service; diff --git a/src-tauri/src/services/system_info_service.rs b/src-tauri/src/services/system_info_service.rs new file mode 100644 index 0000000..8b5b18d --- /dev/null +++ b/src-tauri/src/services/system_info_service.rs @@ -0,0 +1,193 @@ +use crate::utils::unit; +use crate::{log_debug, log_error, log_info, log_internal}; + +use serde::{Deserialize, Serialize}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::MutexGuard; +use std::thread; +use sysinfo::System; +use wmi::{COMLibrary, WMIConnection}; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CpuInfo { + name: String, + vendor: String, + core_count: usize, + frequency: u64, + cpu_name: String, +} + +/// +/// ## CPU情報を取得 +/// +pub fn get_cpu_info(system: MutexGuard<'_, System>) -> Result { + let cpus = system.cpus(); + + if cpus.is_empty() { + return Err("CPU information not available".to_string()); + } + + // CPU情報を収集 + let cpu_info = CpuInfo { + name: cpus[0].brand().to_string(), + vendor: cpus[0].vendor_id().to_string(), + core_count: cpus.len(), + frequency: cpus[0].frequency(), + cpu_name: cpus[0].name().to_string(), + }; + + Ok(cpu_info) +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MemoryInfo { + size: String, + clock: u64, + clock_unit: String, + memory_count: usize, + memory_type: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "PascalCase")] +struct Win32PhysicalMemory { + capacity: u64, + speed: u32, + memory_type: Option, +} + +/// +/// ## メモリ情報を取得 +/// +pub fn get_memory_info() -> Result { + let results = get_memory_info_in_thread()?; + + log_info!( + &format!("mem info: {:?}", results), + "get_memory_info", + None::<&str> + ); + + let memory_info = MemoryInfo { + size: unit::format_size(results.iter().map(|mem| mem.capacity).sum()), + clock: results[0].speed as u64, + clock_unit: "MHz".to_string(), + memory_count: results.len(), + memory_type: get_memory_type_description(results[0].memory_type), + }; + + Ok(memory_info) +} + +/// +/// ## MemoryTypeの値に対応するメモリの種類を文字列で返す +/// +/// - [TODO] DDR5に対応する +/// +fn get_memory_type_description(memory_type: Option) -> String { + log_info!( + &format!("mem type: {:?}", memory_type), + "get_memory_type_description", + None::<&str> + ); + + match memory_type { + Some(0) => "Unknown or Unsupported".to_string(), + Some(1) => "Other".to_string(), + Some(2) => "DRAM".to_string(), + Some(3) => "Synchronous DRAM".to_string(), + Some(4) => "Cache DRAM".to_string(), + Some(5) => "EDO".to_string(), + Some(6) => "EDRAM".to_string(), + Some(7) => "VRAM".to_string(), + Some(8) => "SRAM".to_string(), + Some(9) => "RAM".to_string(), + Some(10) => "ROM".to_string(), + Some(11) => "Flash".to_string(), + Some(12) => "EEPROM".to_string(), + Some(13) => "FEPROM".to_string(), + Some(14) => "EPROM".to_string(), + Some(15) => "CDRAM".to_string(), + Some(16) => "3DRAM".to_string(), + Some(17) => "SDRAM".to_string(), + Some(18) => "SGRAM".to_string(), + Some(19) => "RDRAM".to_string(), + Some(20) => "DDR".to_string(), + Some(21) => "DDR2".to_string(), + Some(22) => "DDR2 FB-DIMM".to_string(), + Some(24) => "DDR3".to_string(), + Some(25) => "FBD2".to_string(), + Some(26) => "DDR4".to_string(), + Some(mt) => format!("Other or Unknown Memory Type ({})", mt), + None => "Unknown".to_string(), + } +} + +/// +/// TODO +/// +fn get_memory_type_with_fallback( + memory_type: Option, + smbios_memory_type: Option, +) -> String { + match memory_type { + Some(0) => match smbios_memory_type { + Some(20) => "DDR".to_string(), + Some(21) => "DDR2".to_string(), + Some(24) => "DDR3".to_string(), + Some(26) => "DDR4".to_string(), + Some(31) => "DDR5".to_string(), + Some(mt) => format!("Other SMBIOS Memory Type ({})", mt), + None => "Unknown".to_string(), + }, + Some(mt) => get_memory_type_description(Some(mt)), + None => "Unknown".to_string(), + } +} + +/// +/// ## メモリ情報を別スレッドで取得する(WMIを使用) +/// +fn get_memory_info_in_thread() -> Result, String> { + let (tx, rx): ( + Sender, String>>, + Receiver, String>>, + ) = channel(); + + // 別スレッドを起動してWMIクエリを実行 + thread::spawn(move || { + let result = (|| { + let com_con = COMLibrary::new() + .map_err(|e| format!("Failed to initialize COM Library: {:?}", e))?; + let wmi_con = WMIConnection::new(com_con) + .map_err(|e| format!("Failed to create WMI connection: {:?}", e))?; + + // WMIクエリを実行してメモリ情報を取得 + let results: Vec = wmi_con + .raw_query("SELECT Capacity, Speed, MemoryType FROM Win32_PhysicalMemory") + .map_err(|e| format!("Failed to execute query: {:?}", e))?; + + log_info!( + &format!("mem info: {:?}", results), + "get_memory_info_in_thread", + None::<&str> + ); + + Ok(results) + })(); + + // メインスレッドに結果を送信 + if let Err(err) = tx.send(result) { + log_error!( + "Failed to send data from thread", + "get_wmi_data_in_thread", + Some(err.to_string()) + ); + } + }); + + // メインスレッドで結果を受信 + rx.recv().map_err(|_| "Failed to receive data from thread".to_string())? +} diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 4ccd0f8..171d58a 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod file; pub mod logger; +pub mod unit; diff --git a/src-tauri/src/utils/unit.rs b/src-tauri/src/utils/unit.rs new file mode 100644 index 0000000..893e565 --- /dev/null +++ b/src-tauri/src/utils/unit.rs @@ -0,0 +1,16 @@ +/// +/// ## バイト数を単位付きの文字列に変換 +/// +pub fn format_size(bytes: u64) -> String { + const KILOBYTE: u64 = 1024; + const MEGABYTE: u64 = KILOBYTE * 1024; + const GIGABYTE: u64 = MEGABYTE * 1024; + + if bytes >= GIGABYTE { + format!("{:.2} GB", bytes as f64 / GIGABYTE as f64) + } else if bytes >= MEGABYTE { + format!("{:.2} MB", bytes as f64 / MEGABYTE as f64) + } else { + format!("{} bytes", bytes) + } +} diff --git a/src/App.tsx b/src/App.tsx index 26febd0..e97aa04 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,9 +6,9 @@ import { useErrorModalListener, useSettingsModalListener, } from "@/hooks/useTauriEventListener"; -import { useSettingsAtom } from "./atom/useSettingsAtom"; - import SettingsSheet from "@/template/SettingsSheet"; +import { useHardwareInfoAtom } from "./atom/useHardwareInfoAtom"; +import { useSettingsAtom } from "./atom/useSettingsAtom"; import { useDarkMode } from "./hooks/useDarkMode"; type ButtonState = "chart" | "raw"; @@ -20,6 +20,7 @@ const Page = () => { useSettingsModalListener(); useErrorModalListener(); + useHardwareInfoAtom(); const handleShowData = () => { setButtonState(buttonState === "raw" ? "chart" : "raw"); diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts new file mode 100644 index 0000000..a23585c --- /dev/null +++ b/src/atom/useHardwareInfoAtom.ts @@ -0,0 +1,30 @@ +import { getHardwareInfo } from "@/services/hardwareService"; +import type { HardwareInfo } from "@/types/hardwareDataType"; +import { atom, useAtom } from "jotai"; +import { useEffect } from "react"; + +const hardInfoAtom = atom(); + +export const useHardwareInfoAtom = () => { + const [hardware, setHardInfo] = useAtom(hardInfoAtom); + + useEffect(() => { + const init = async () => { + try { + const hardwareInfo = await getHardwareInfo(); + setHardInfo(hardwareInfo); + } catch (e) { + console.error(e); + } + }; + + // データがなければ取得して更新 + if (!hardware) { + init(); + } + + console.log(hardware); + }, [setHardInfo, hardware]); + + return { hardware }; +}; diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index 7988453..f4c5c93 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -1,9 +1,14 @@ +import type { HardwareInfo } from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; export const getCpuUsage = async (): Promise => { return await invoke("get_cpu_usage"); }; +export const getHardwareInfo = async (): Promise => { + return await invoke("get_hardware_info"); +}; + export const getMemoryUsage = async (): Promise => { return await invoke("get_memory_usage"); }; diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index 74f5da6..1cd566c 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -1 +1,24 @@ export type ChartDataType = "cpu" | "memory" | "gpu"; + +export type CpuInfo = { + name: string; + clock: number; + clockUnit: string; + vendor: string; + coreCount: number; + frequency: number; + cpuName: string; +}; + +export type MemoryInfo = { + size: string; + clock: number; + clockUnit: string; + memoryCount: number; + memoryType: string; +}; + +export type HardwareInfo = { + cpu: CpuInfo; + memory: MemoryInfo; +}; From 75e3691626e7356286b9fd8b46b68690ca81f111 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 02:34:20 +0900 Subject: [PATCH 17/43] change: indent tab => space --- biome.json | 28 +-- src/App.tsx | 56 +++--- src/atom/ui.ts | 2 +- src/atom/useHardwareInfoAtom.ts | 34 ++-- src/atom/useSettingsAtom.ts | 70 +++---- src/components/Sample.tsx | 106 +++++------ src/components/charts/CustomLegend.tsx | 32 ++-- src/components/charts/LineChart.tsx | 248 ++++++++++++------------- src/components/ui/button.tsx | 82 ++++---- src/components/ui/input.tsx | 28 +-- src/components/ui/label.tsx | 18 +- src/components/ui/sheet.tsx | 178 +++++++++--------- src/components/ui/switch.tsx | 32 ++-- src/consts/chart.ts | 8 +- src/hooks/useDarkMode.ts | 36 ++-- src/hooks/useHardwareData.ts | 80 ++++---- src/hooks/useTauriEventListener.ts | 60 +++--- src/lib/utils.ts | 2 +- src/main.tsx | 6 +- src/services/hardwareService.ts | 14 +- src/services/settingService.ts | 8 +- src/template/Chart.tsx | 72 +++---- src/template/SettingsSheet.tsx | 172 ++++++++--------- src/types/hardwareDataType.ts | 28 +-- src/types/settingsType.ts | 6 +- 25 files changed, 705 insertions(+), 701 deletions(-) diff --git a/biome.json b/biome.json index 36e3fcc..edbde09 100644 --- a/biome.json +++ b/biome.json @@ -1,12 +1,16 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - } -} +{ + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "formatter": { + "indentStyle": "space", + "indentWidth": 2 + } +} diff --git a/src/App.tsx b/src/App.tsx index e97aa04..cd0c942 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,8 +3,8 @@ import TestTemplate from "./components/Sample"; import ChartTemplate from "./template/Chart"; import "./index.css"; import { - useErrorModalListener, - useSettingsModalListener, + useErrorModalListener, + useSettingsModalListener, } from "@/hooks/useTauriEventListener"; import SettingsSheet from "@/template/SettingsSheet"; import { useHardwareInfoAtom } from "./atom/useHardwareInfoAtom"; @@ -14,36 +14,36 @@ import { useDarkMode } from "./hooks/useDarkMode"; type ButtonState = "chart" | "raw"; const Page = () => { - const [buttonState, setButtonState] = useState("chart"); - const { settings } = useSettingsAtom(); - const { toggle } = useDarkMode(); + const [buttonState, setButtonState] = useState("chart"); + const { settings } = useSettingsAtom(); + const { toggle } = useDarkMode(); - useSettingsModalListener(); - useErrorModalListener(); - useHardwareInfoAtom(); + useSettingsModalListener(); + useErrorModalListener(); + useHardwareInfoAtom(); - const handleShowData = () => { - setButtonState(buttonState === "raw" ? "chart" : "raw"); - }; + const handleShowData = () => { + setButtonState(buttonState === "raw" ? "chart" : "raw"); + }; - useEffect(() => { - if (settings?.theme) { - toggle(settings.theme === "dark"); - } - }, [settings?.theme, toggle]); + useEffect(() => { + if (settings?.theme) { + toggle(settings.theme === "dark"); + } + }, [settings?.theme, toggle]); - return ( -
-

Hardware Monitor Proto

-
- -
- {buttonState === "raw" ? : } - -
- ); + return ( +
+

Hardware Monitor Proto

+
+ +
+ {buttonState === "raw" ? : } + +
+ ); }; export default Page; diff --git a/src/atom/ui.ts b/src/atom/ui.ts index 2475c96..d6cf2d9 100644 --- a/src/atom/ui.ts +++ b/src/atom/ui.ts @@ -1,5 +1,5 @@ import { atom } from "jotai"; export const modalAtoms = { - showSettingsModal: atom(false), + showSettingsModal: atom(false), }; diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index a23585c..637552f 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -6,25 +6,25 @@ import { useEffect } from "react"; const hardInfoAtom = atom(); export const useHardwareInfoAtom = () => { - const [hardware, setHardInfo] = useAtom(hardInfoAtom); + const [hardware, setHardInfo] = useAtom(hardInfoAtom); - useEffect(() => { - const init = async () => { - try { - const hardwareInfo = await getHardwareInfo(); - setHardInfo(hardwareInfo); - } catch (e) { - console.error(e); - } - }; + useEffect(() => { + const init = async () => { + try { + const hardwareInfo = await getHardwareInfo(); + setHardInfo(hardwareInfo); + } catch (e) { + console.error(e); + } + }; - // データがなければ取得して更新 - if (!hardware) { - init(); - } + // データがなければ取得して更新 + if (!hardware) { + init(); + } - console.log(hardware); - }, [setHardInfo, hardware]); + console.log(hardware); + }, [setHardInfo, hardware]); - return { hardware }; + return { hardware }; }; diff --git a/src/atom/useSettingsAtom.ts b/src/atom/useSettingsAtom.ts index 486b714..f8f71f7 100644 --- a/src/atom/useSettingsAtom.ts +++ b/src/atom/useSettingsAtom.ts @@ -1,51 +1,51 @@ import { - getSettings, - setDisplayTargets, - setTheme, + getSettings, + setDisplayTargets, + setTheme, } from "@/services/settingService"; import type { ChartDataType } from "@/types/hardwareDataType"; import type { Settings } from "@/types/settingsType"; import { atom, useAtom } from "jotai"; import { useEffect } from "react"; const settingsAtom = atom({ - language: "en", - theme: "light", - display_targets: [], + language: "en", + theme: "light", + display_targets: [], }); export const useSettingsAtom = () => { - const [settings, setSettings] = useAtom(settingsAtom); + const [settings, setSettings] = useAtom(settingsAtom); - useEffect(() => { - const loadSettings = async () => { - const setting = await getSettings(); - setSettings(setting); - }; - loadSettings(); - }, [setSettings]); + useEffect(() => { + const loadSettings = async () => { + const setting = await getSettings(); + setSettings(setting); + }; + loadSettings(); + }, [setSettings]); - const toggleDisplayTarget = async (target: ChartDataType) => { - const newTargets = settings.display_targets.includes(target) - ? settings.display_targets.filter((t) => t !== target) - : [...settings.display_targets, target]; + const toggleDisplayTarget = async (target: ChartDataType) => { + const newTargets = settings.display_targets.includes(target) + ? settings.display_targets.filter((t) => t !== target) + : [...settings.display_targets, target]; - try { - // [TODO] Result型を作りたい - await setDisplayTargets(newTargets); - setSettings((prev) => ({ ...prev, display_targets: newTargets })); - } catch (e) { - console.error(e); - } - }; + try { + // [TODO] Result型を作りたい + await setDisplayTargets(newTargets); + setSettings((prev) => ({ ...prev, display_targets: newTargets })); + } catch (e) { + console.error(e); + } + }; - const toggleTheme = async (theme: "light" | "dark") => { - try { - await setTheme(theme); - setSettings((prev) => ({ ...prev, theme })); - } catch (e) { - console.error(e); - } - }; + const toggleTheme = async (theme: "light" | "dark") => { + try { + await setTheme(theme); + setSettings((prev) => ({ ...prev, theme })); + } catch (e) { + console.error(e); + } + }; - return { settings, toggleDisplayTarget, toggleTheme }; + return { settings, toggleDisplayTarget, toggleTheme }; }; diff --git a/src/components/Sample.tsx b/src/components/Sample.tsx index 955d86e..8377bb2 100644 --- a/src/components/Sample.tsx +++ b/src/components/Sample.tsx @@ -1,66 +1,66 @@ import { - getCpuMemoryHistory, - getCpuUsage, - getCpuUsageHistory, - getGpuUsage, - getGpuUsageHistory, - getMemoryUsage, + getCpuMemoryHistory, + getCpuUsage, + getCpuUsageHistory, + getGpuUsage, + getGpuUsageHistory, + getMemoryUsage, } from "@/services/hardwareService"; import { useEffect, useState } from "react"; const Sample = () => { - const [cpuUsage, setCpuUsage] = useState(0); - const [memoryUsage, setMemoryUsage] = useState(0); - const [gpuUsage, setGpuUsage] = useState(0); + const [cpuUsage, setCpuUsage] = useState(0); + const [memoryUsage, setMemoryUsage] = useState(0); + const [gpuUsage, setGpuUsage] = useState(0); - const [cpuHistory, setCpuHistory] = useState([]); - const [memoryHistory, setMemoryHistory] = useState([]); - const [gpuHistory, setGpuHistory] = useState([]); + const [cpuHistory, setCpuHistory] = useState([]); + const [memoryHistory, setMemoryHistory] = useState([]); + const [gpuHistory, setGpuHistory] = useState([]); - useEffect(() => { - const interval = setInterval(async () => { - setCpuUsage(await getCpuUsage()); - setMemoryUsage(await getMemoryUsage()); - setCpuHistory(await getCpuUsageHistory(30)); - setMemoryHistory(await getCpuMemoryHistory(30)); - setGpuUsage(await getGpuUsage()); - setGpuHistory(await getGpuUsageHistory(30)); - }, 1000); + useEffect(() => { + const interval = setInterval(async () => { + setCpuUsage(await getCpuUsage()); + setMemoryUsage(await getMemoryUsage()); + setCpuHistory(await getCpuUsageHistory(30)); + setMemoryHistory(await getCpuMemoryHistory(30)); + setGpuUsage(await getGpuUsage()); + setGpuHistory(await getGpuUsageHistory(30)); + }, 1000); - return () => clearInterval(interval); - }, []); + return () => clearInterval(interval); + }, []); - return ( -
-

CPU: {cpuUsage}%

-

MEMORY: {memoryUsage}%

-

GPU: {gpuUsage}%

+ return ( +
+

CPU: {cpuUsage}%

+

MEMORY: {memoryUsage}%

+

GPU: {gpuUsage}%

-
-

CPU History

-

Count: {cpuHistory.length}

-
    - {cpuHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-

MEMORY History

-

Count: {memoryHistory.length}

-
    - {memoryHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-

GPU History

-

Count: {gpuHistory.length}

-
    - {gpuHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-
-
- ); +
+

CPU History

+

Count: {cpuHistory.length}

+
    + {cpuHistory.map((item, index) => ( +
  • {item}
  • + ))} +
+

MEMORY History

+

Count: {memoryHistory.length}

+
    + {memoryHistory.map((item, index) => ( +
  • {item}
  • + ))} +
+

GPU History

+

Count: {gpuHistory.length}

+
    + {gpuHistory.map((item, index) => ( +
  • {item}
  • + ))} +
+
+
+ ); }; export default Sample; diff --git a/src/components/charts/CustomLegend.tsx b/src/components/charts/CustomLegend.tsx index 08140c5..a91d95a 100644 --- a/src/components/charts/CustomLegend.tsx +++ b/src/components/charts/CustomLegend.tsx @@ -1,25 +1,25 @@ export type LegendItem = { - label: string; - icon: JSX.Element; - datasetIndex: number; + label: string; + icon: JSX.Element; + datasetIndex: number; }; const CustomLegend = ({ - item, + item, }: { - item: LegendItem; + item: LegendItem; }) => { - return ( -
-
- {item.icon} - {item.label} -
-
- ); + return ( +
+
+ {item.icon} + {item.label} +
+
+ ); }; export default CustomLegend; diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index 5c882c6..18711ba 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -1,16 +1,16 @@ import type { ChartDataType } from "@/types/hardwareDataType"; import { Cpu, GraphicsCard, Memory } from "@phosphor-icons/react"; import { - CategoryScale, - Chart as ChartJS, - type ChartOptions, - Filler, - Legend, - LineElement, - LinearScale, - PointElement, - Title, - Tooltip, + CategoryScale, + Chart as ChartJS, + type ChartOptions, + Filler, + Legend, + LineElement, + LinearScale, + PointElement, + Title, + Tooltip, } from "chart.js"; import type { Chart, ChartData } from "chart.js"; import { useRef } from "react"; @@ -18,129 +18,129 @@ import { Line } from "react-chartjs-2"; import CustomLegend, { type LegendItem } from "./CustomLegend"; ChartJS.register( - CategoryScale, - LinearScale, - PointElement, - LineElement, - Title, - Tooltip, - Legend, - Filler, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + Filler, ); const LineChart = ({ - labels, - chartData, - dataType, + labels, + chartData, + dataType, }: { - labels: string[]; - chartData: number[]; - dataType: ChartDataType; + labels: string[]; + chartData: number[]; + dataType: ChartDataType; }) => { - const chartRef = useRef>(null); + const chartRef = useRef>(null); - const options: ChartOptions<"line"> = { - responsive: true, - animation: false, - scales: { - x: { display: false }, - y: { - display: true, - suggestedMin: 0, - suggestedMax: 100, - grid: { color: "rgba(255, 255, 255, 0.2)" }, - ticks: { color: "#fff" }, - }, - }, - elements: { - point: { radius: 0, hoverRadius: 0 }, - line: { tension: 0.4 }, - }, - plugins: { - legend: { display: false }, - tooltip: { - backgroundColor: "rgba(0, 0, 0, 0.7)", - titleColor: "#fff", - bodyColor: "#fff", - }, - }, - }; + const options: ChartOptions<"line"> = { + responsive: true, + animation: false, + scales: { + x: { display: false }, + y: { + display: true, + suggestedMin: 0, + suggestedMax: 100, + grid: { color: "rgba(255, 255, 255, 0.2)" }, + ticks: { color: "#fff" }, + }, + }, + elements: { + point: { radius: 0, hoverRadius: 0 }, + line: { tension: 0.4 }, + }, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: "rgba(0, 0, 0, 0.7)", + titleColor: "#fff", + bodyColor: "#fff", + }, + }, + }; - const data: Record> = { - cpu: { - labels, - datasets: [ - { - label: "CPU Usage (%)", - data: chartData, - borderColor: "rgb(75, 192, 192)", - backgroundColor: "rgba(75, 192, 192, 0.3)", - fill: true, - }, - ], - }, - memory: { - labels, - datasets: [ - { - label: "Memory Usage (%)", - data: chartData, - borderColor: "rgb(255, 99, 132)", - backgroundColor: "rgba(255, 99, 132, 0.3)", - fill: true, - }, - ], - }, - gpu: { - labels, - datasets: [ - { - label: "GPU Usage (%)", - data: chartData, - borderColor: "rgb(255, 206, 86)", - backgroundColor: "rgba(255, 206, 86, 0.3)", - fill: true, - }, - ], - }, - }; + const data: Record> = { + cpu: { + labels, + datasets: [ + { + label: "CPU Usage (%)", + data: chartData, + borderColor: "rgb(75, 192, 192)", + backgroundColor: "rgba(75, 192, 192, 0.3)", + fill: true, + }, + ], + }, + memory: { + labels, + datasets: [ + { + label: "Memory Usage (%)", + data: chartData, + borderColor: "rgb(255, 99, 132)", + backgroundColor: "rgba(255, 99, 132, 0.3)", + fill: true, + }, + ], + }, + gpu: { + labels, + datasets: [ + { + label: "GPU Usage (%)", + data: chartData, + borderColor: "rgb(255, 206, 86)", + backgroundColor: "rgba(255, 206, 86, 0.3)", + fill: true, + }, + ], + }, + }; - const legendItems: Record = { - cpu: { - label: "CPU Usage", - icon: ( - - ), - datasetIndex: 0, - }, - memory: { - label: "Memory Usage", - icon: ( - - ), - datasetIndex: 1, - }, - gpu: { - label: "GPU Usage", - icon: ( - - ), - datasetIndex: 2, - }, - }; + const legendItems: Record = { + cpu: { + label: "CPU Usage", + icon: ( + + ), + datasetIndex: 0, + }, + memory: { + label: "Memory Usage", + icon: ( + + ), + datasetIndex: 1, + }, + gpu: { + label: "GPU Usage", + icon: ( + + ), + datasetIndex: 2, + }, + }; - return ( -
- -
- -
-
- ); + return ( +
+ +
+ +
+
+ ); }; export default LineChart; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index aa4de5e..7e2c0c4 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,53 +5,53 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", - { - variants: { - variant: { - default: - "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", - destructive: - "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", - outline: - "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - secondary: - "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", - ghost: - "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", + { + variants: { + variant: { + default: + "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", + destructive: + "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", + outline: + "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + secondary: + "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", + ghost: + "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, ); export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - - ); - }, + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, ); Button.displayName = "Button"; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index d76fc7c..0bfebd2 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -3,22 +3,22 @@ import * as React from "react"; import { cn } from "@/lib/utils"; export interface InputProps - extends React.InputHTMLAttributes {} + extends React.InputHTMLAttributes {} const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ); - }, + ({ className, type, ...props }, ref) => { + return ( + + ); + }, ); Input.displayName = "Input"; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 6e126e9..a115d28 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -5,19 +5,19 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const labelVariants = cva( - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", ); const Label = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & - VariantProps + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps >(({ className, ...props }, ref) => ( - + )); Label.displayName = LabelPrimitive.Root.displayName; diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index ff823cc..4b7f44a 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -14,128 +14,128 @@ const SheetClose = SheetPrimitive.Close; const SheetPortal = SheetPrimitive.Portal; const SheetOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950", - { - variants: { - side: { - top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", - bottom: - "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", - left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", - right: - "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", - }, - }, - defaultVariants: { - side: "right", - }, - }, + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 dark:bg-neutral-950", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + }, ); interface SheetContentProps - extends React.ComponentPropsWithoutRef, - VariantProps {} + extends React.ComponentPropsWithoutRef, + VariantProps {} const SheetContent = React.forwardRef< - React.ElementRef, - SheetContentProps + React.ElementRef, + SheetContentProps >(({ side = "right", className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - + + + + {children} + + + Close + + + )); SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
+
); SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ - className, - ...props + className, + ...props }: React.HTMLAttributes) => ( -
+
); SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); SheetDescription.displayName = SheetPrimitive.Description.displayName; export { - Sheet, - SheetPortal, - SheetOverlay, - SheetTrigger, - SheetClose, - SheetContent, - SheetHeader, - SheetFooter, - SheetTitle, - SheetDescription, + Sheet, + SheetPortal, + SheetOverlay, + SheetTrigger, + SheetClose, + SheetContent, + SheetHeader, + SheetFooter, + SheetTitle, + SheetDescription, }; diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index a190d40..6ebabad 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -4,23 +4,23 @@ import * as React from "react"; import { cn } from "@/lib/utils"; const Switch = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - - - + + + )); Switch.displayName = SwitchPrimitives.Root.displayName; diff --git a/src/consts/chart.ts b/src/consts/chart.ts index bd3d409..f5a4845 100644 --- a/src/consts/chart.ts +++ b/src/consts/chart.ts @@ -1,6 +1,6 @@ export const chartConfig = { - /** - * グラフの履歴の長さ(秒) - */ - historyLengthSec: 60, + /** + * グラフの履歴の長さ(秒) + */ + historyLengthSec: 60, } as const; diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts index 107aa92..31da57b 100644 --- a/src/hooks/useDarkMode.ts +++ b/src/hooks/useDarkMode.ts @@ -1,28 +1,28 @@ import { useCallback, useEffect, useState } from "react"; type UseSimpleDarkMode = (isDark?: boolean) => { - isDarkMode: boolean; - toggle: (isDark?: boolean) => void; + isDarkMode: boolean; + toggle: (isDark?: boolean) => void; }; export const useDarkMode: UseSimpleDarkMode = (isInitialDark = false) => { - const [isDarkMode, toggleTheme] = useState(isInitialDark); - const toggle = useCallback((isDark?: boolean) => { - if (typeof isDark === "undefined") { - toggleTheme((state) => !state); - return; - } + const [isDarkMode, toggleTheme] = useState(isInitialDark); + const toggle = useCallback((isDark?: boolean) => { + if (typeof isDark === "undefined") { + toggleTheme((state) => !state); + return; + } - toggleTheme(isDark); - }, []); + toggleTheme(isDark); + }, []); - useEffect(() => { - if (isDarkMode) { - document.documentElement.classList.add("dark"); - } else { - document.documentElement.classList.remove("dark"); - } - }, [isDarkMode]); + useEffect(() => { + if (isDarkMode) { + document.documentElement.classList.add("dark"); + } else { + document.documentElement.classList.remove("dark"); + } + }, [isDarkMode]); - return { isDarkMode, toggle }; + return { isDarkMode, toggle }; }; diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts index 9e91bbb..8323b71 100644 --- a/src/hooks/useHardwareData.ts +++ b/src/hooks/useHardwareData.ts @@ -1,61 +1,61 @@ import { - cpuUsageHistoryAtom, - graphicUsageHistoryAtom, - memoryUsageHistoryAtom, + cpuUsageHistoryAtom, + graphicUsageHistoryAtom, + memoryUsageHistoryAtom, } from "@/atom/chart"; import { chartConfig } from "@/consts/chart"; import { - getCpuUsage, - getGpuUsage, - getMemoryUsage, + getCpuUsage, + getGpuUsage, + getMemoryUsage, } from "@/services/hardwareService"; import type { ChartDataType } from "@/types/hardwareDataType"; import { type PrimitiveAtom, useSetAtom } from "jotai"; import { useEffect } from "react"; type AtomActionMapping = { - atom: PrimitiveAtom; - action: () => Promise; + atom: PrimitiveAtom; + action: () => Promise; }; /** * ハードウェア使用率の履歴を更新する */ export const useUsageUpdater = (dataType: ChartDataType) => { - const mapping: Record = { - cpu: { - atom: cpuUsageHistoryAtom, - action: getCpuUsage, - }, - memory: { - atom: memoryUsageHistoryAtom, - action: getMemoryUsage, - }, - gpu: { - atom: graphicUsageHistoryAtom, - action: getGpuUsage, - }, - }; + const mapping: Record = { + cpu: { + atom: cpuUsageHistoryAtom, + action: getCpuUsage, + }, + memory: { + atom: memoryUsageHistoryAtom, + action: getMemoryUsage, + }, + gpu: { + atom: graphicUsageHistoryAtom, + action: getGpuUsage, + }, + }; - const setHistory = useSetAtom(mapping[dataType].atom); - const getUsage = mapping[dataType].action; + const setHistory = useSetAtom(mapping[dataType].atom); + const getUsage = mapping[dataType].action; - useEffect(() => { - const intervalId = setInterval(async () => { - const usage = await getUsage(); - setHistory((prev) => { - const newHistory = [...prev, usage]; + useEffect(() => { + const intervalId = setInterval(async () => { + const usage = await getUsage(); + setHistory((prev) => { + const newHistory = [...prev, usage]; - // 履歴保持数に満たない場合は0で埋める - const paddedHistory = Array( - Math.max(chartConfig.historyLengthSec - newHistory.length, 0), - ) - .fill(null) - .concat(newHistory); - return paddedHistory.slice(-chartConfig.historyLengthSec); - }); - }, 1000); + // 履歴保持数に満たない場合は0で埋める + const paddedHistory = Array( + Math.max(chartConfig.historyLengthSec - newHistory.length, 0), + ) + .fill(null) + .concat(newHistory); + return paddedHistory.slice(-chartConfig.historyLengthSec); + }); + }, 1000); - return () => clearInterval(intervalId); - }, [setHistory, getUsage]); + return () => clearInterval(intervalId); + }, [setHistory, getUsage]); }; diff --git a/src/hooks/useTauriEventListener.ts b/src/hooks/useTauriEventListener.ts index b19f58b..4705fd2 100644 --- a/src/hooks/useTauriEventListener.ts +++ b/src/hooks/useTauriEventListener.ts @@ -5,13 +5,13 @@ import { useEffect } from "react"; import { modalAtoms } from "../atom/ui"; const useTauriEventListener = (event: string, callback: () => void) => { - useEffect(() => { - const unListen = listen(event, callback); + useEffect(() => { + const unListen = listen(event, callback); - return () => { - unListen.then((unListen) => unListen()); - }; - }, [event, callback]); + return () => { + unListen.then((unListen) => unListen()); + }; + }, [event, callback]); }; /** @@ -20,37 +20,37 @@ const useTauriEventListener = (event: string, callback: () => void) => { * @returns closeModal */ export const useSettingsModalListener = () => { - const setShowSettingsModal = useSetAtom(modalAtoms.showSettingsModal); + const setShowSettingsModal = useSetAtom(modalAtoms.showSettingsModal); - // モーダルを開くイベントをリッスン - useTauriEventListener("open_settings", () => { - setShowSettingsModal(true); - }); + // モーダルを開くイベントをリッスン + useTauriEventListener("open_settings", () => { + setShowSettingsModal(true); + }); - const closeModal = () => setShowSettingsModal(false); + const closeModal = () => setShowSettingsModal(false); - return { closeModal }; + return { closeModal }; }; /** * バックエンド側のエラーイベントをリッスンして、エラーダイヤログを表示する */ export const useErrorModalListener = () => { - useEffect(() => { - const unListen = listen("error_event", (event) => { - const { title, message: errorMessage } = event.payload as { - title: string; - message: string; - }; - - message(errorMessage, { - title: title, - type: "error", - }); - }); - - return () => { - unListen.then((off) => off()); - }; - }, []); + useEffect(() => { + const unListen = listen("error_event", (event) => { + const { title, message: errorMessage } = event.payload as { + title: string; + message: string; + }; + + message(errorMessage, { + title: title, + type: "error", + }); + }); + + return () => { + unListen.then((off) => off()); + }; + }, []); }; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index ac680b3..365058c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -2,5 +2,5 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); + return twMerge(clsx(inputs)); } diff --git a/src/main.tsx b/src/main.tsx index c08eb09..2be325e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - , + + + , ); diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index f4c5c93..3c13a78 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -2,29 +2,29 @@ import type { HardwareInfo } from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; export const getCpuUsage = async (): Promise => { - return await invoke("get_cpu_usage"); + return await invoke("get_cpu_usage"); }; export const getHardwareInfo = async (): Promise => { - return await invoke("get_hardware_info"); + return await invoke("get_hardware_info"); }; export const getMemoryUsage = async (): Promise => { - return await invoke("get_memory_usage"); + return await invoke("get_memory_usage"); }; export const getCpuUsageHistory = (seconds: number): Promise => { - return invoke("get_cpu_usage_history", { seconds: seconds }); + return invoke("get_cpu_usage_history", { seconds: seconds }); }; export const getCpuMemoryHistory = (seconds: number): Promise => { - return invoke("get_memory_usage_history", { seconds: seconds }); + return invoke("get_memory_usage_history", { seconds: seconds }); }; export const getGpuUsage = async (): Promise => { - return await invoke("get_gpu_usage"); + return await invoke("get_gpu_usage"); }; export const getGpuUsageHistory = (seconds: number): Promise => { - return invoke("get_gpu_usage_history", { seconds: seconds }); + return invoke("get_gpu_usage_history", { seconds: seconds }); }; diff --git a/src/services/settingService.ts b/src/services/settingService.ts index d56b2e6..3eec3ad 100644 --- a/src/services/settingService.ts +++ b/src/services/settingService.ts @@ -2,15 +2,15 @@ import type { Settings } from "@/types/settingsType"; import { invoke } from "@tauri-apps/api/tauri"; export const getSettings = async (): Promise => { - return await invoke("get_settings"); + return await invoke("get_settings"); }; export const setTheme = async (theme: Settings["theme"]): Promise => { - return await invoke("set_theme", { newTheme: theme }); + return await invoke("set_theme", { newTheme: theme }); }; export const setDisplayTargets = async ( - targets: Settings["display_targets"], + targets: Settings["display_targets"], ): Promise => { - return await invoke("set_display_targets", { newTargets: targets }); + return await invoke("set_display_targets", { newTargets: targets }); }; diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index 1c4e866..c49192f 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -1,7 +1,7 @@ import { - cpuUsageHistoryAtom, - graphicUsageHistoryAtom, - memoryUsageHistoryAtom, + cpuUsageHistoryAtom, + graphicUsageHistoryAtom, + memoryUsageHistoryAtom, } from "@/atom/chart"; import { useSettingsAtom } from "@/atom/useSettingsAtom"; import LineChart from "@/components/charts/LineChart"; @@ -14,50 +14,50 @@ import { useMemo } from "react"; const labels = Array(chartConfig.historyLengthSec).fill(""); const CpuUsageChart = () => { - const [cpuUsageHistory] = useAtom(cpuUsageHistoryAtom); - useUsageUpdater("cpu"); + const [cpuUsageHistory] = useAtom(cpuUsageHistoryAtom); + useUsageUpdater("cpu"); - return ( - - ); + return ( + + ); }; const MemoryUsageChart = () => { - const [memoryUsageHistory] = useAtom(memoryUsageHistoryAtom); - useUsageUpdater("memory"); - - return ( - - ); + const [memoryUsageHistory] = useAtom(memoryUsageHistoryAtom); + useUsageUpdater("memory"); + + return ( + + ); }; const GpuUsageChart = () => { - const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); - useUsageUpdater("gpu"); + const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); + useUsageUpdater("gpu"); - return ( - - ); + return ( + + ); }; const ChartTemplate = () => { - const { settings } = useSettingsAtom(); - - const renderedCharts = useMemo(() => { - return ( - <> - {settings?.display_targets.includes("cpu") && } - {settings?.display_targets.includes("memory") && } - {settings?.display_targets.includes("gpu") && } - - ); - }, [settings]); - - return
{renderedCharts}
; + const { settings } = useSettingsAtom(); + + const renderedCharts = useMemo(() => { + return ( + <> + {settings?.display_targets.includes("cpu") && } + {settings?.display_targets.includes("memory") && } + {settings?.display_targets.includes("gpu") && } + + ); + }, [settings]); + + return
{renderedCharts}
; }; export default ChartTemplate; diff --git a/src/template/SettingsSheet.tsx b/src/template/SettingsSheet.tsx index 65c0c2b..2e0d142 100644 --- a/src/template/SettingsSheet.tsx +++ b/src/template/SettingsSheet.tsx @@ -3,109 +3,109 @@ import { useSettingsAtom } from "@/atom/useSettingsAtom"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, } from "@/components/ui/sheet"; import { useSettingsModalListener } from "@/hooks/useTauriEventListener"; import type { ChartDataType } from "@/types/hardwareDataType"; import { useAtom } from "jotai"; const SettingGraphType = () => { - const { settings, toggleDisplayTarget } = useSettingsAtom(); - const selectedGraphTypes = settings.display_targets; + const { settings, toggleDisplayTarget } = useSettingsAtom(); + const selectedGraphTypes = settings.display_targets; - const toggleGraphType = async (type: ChartDataType) => { - await toggleDisplayTarget(type); - }; + const toggleGraphType = async (type: ChartDataType) => { + await toggleDisplayTarget(type); + }; - return ( -
- -
- - - -
-
- ); + return ( +
+ +
+ + + +
+
+ ); }; const SettingColorMode = () => { - const { settings, toggleTheme } = useSettingsAtom(); + const { settings, toggleTheme } = useSettingsAtom(); - const toggleDarkMode = async (mode: "light" | "dark") => { - await toggleTheme(mode); - }; + const toggleDarkMode = async (mode: "light" | "dark") => { + await toggleTheme(mode); + }; - return ( -
- -
- - -
-
- ); + return ( +
+ +
+ + +
+
+ ); }; const SettingsSheet = () => { - const [showSettingsModal] = useAtom(modalAtoms.showSettingsModal); - const { closeModal } = useSettingsModalListener(); + const [showSettingsModal] = useAtom(modalAtoms.showSettingsModal); + const { closeModal } = useSettingsModalListener(); - return ( - - - - Edit Preference - - Make changes to your preferences here. Click save when you're done. - - -
- - -
-
-
- ); + return ( + + + + Edit Preference + + Make changes to your preferences here. Click save when you're done. + + +
+ + +
+
+
+ ); }; export default SettingsSheet; diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index 1cd566c..796b970 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -1,24 +1,24 @@ export type ChartDataType = "cpu" | "memory" | "gpu"; export type CpuInfo = { - name: string; - clock: number; - clockUnit: string; - vendor: string; - coreCount: number; - frequency: number; - cpuName: string; + name: string; + clock: number; + clockUnit: string; + vendor: string; + coreCount: number; + frequency: number; + cpuName: string; }; export type MemoryInfo = { - size: string; - clock: number; - clockUnit: string; - memoryCount: number; - memoryType: string; + size: string; + clock: number; + clockUnit: string; + memoryCount: number; + memoryType: string; }; export type HardwareInfo = { - cpu: CpuInfo; - memory: MemoryInfo; + cpu: CpuInfo; + memory: MemoryInfo; }; diff --git a/src/types/settingsType.ts b/src/types/settingsType.ts index 98e6dcd..0bf75b4 100644 --- a/src/types/settingsType.ts +++ b/src/types/settingsType.ts @@ -1,7 +1,7 @@ import type { ChartDataType } from "./hardwareDataType"; export type Settings = { - language: string; - theme: "light" | "dark"; - display_targets: Array; + language: string; + theme: "light" | "dark"; + display_targets: Array; }; From ad8a21c4320e4025520e9c6a79785f9ccb61c6a3 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 03:38:18 +0900 Subject: [PATCH 18/43] feature: get gpu info --- src-tauri/src/commands/hardware.rs | 14 +-- src-tauri/src/services/graphic_service.rs | 100 ++++++++++++++++++++++ src/App.tsx | 3 +- src/atom/useHardwareInfoAtom.ts | 10 +-- src/types/hardwareDataType.ts | 10 ++- 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index c3e8604..2f4cc2c 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -48,23 +48,27 @@ pub fn get_cpu_usage(state: tauri::State<'_, AppState>) -> i32 { pub struct SysInfo { pub cpu: system_info_service::CpuInfo, pub memory: system_info_service::MemoryInfo, - //pub gpu: GpuInfo, + pub gpus: Vec, } /// /// ## システム情報を取得 /// #[command] -pub fn get_hardware_info(state: tauri::State<'_, AppState>) -> Result { +pub async fn get_hardware_info( + state: tauri::State<'_, AppState>, +) -> Result { let cpu = system_info_service::get_cpu_info(state.system.lock().unwrap()); let memory = system_info_service::get_memory_info(); + let gpus = graphic_service::get_nvidia_gpu_info().await; - match (cpu, memory) { - (Ok(cpu_info), Ok(memory_info)) => Ok(SysInfo { + match (cpu, memory, gpus) { + (Ok(cpu_info), Ok(memory_info), Ok(gpu_info)) => Ok(SysInfo { cpu: cpu_info, memory: memory_info, + gpus: gpu_info, }), - (Err(e), _) | (_, Err(e)) => { + (Err(e), _, _) | (_, Err(e), _) | (_, _, Err(e)) => { log_error!( "get_sys_info_failed", "get_hardware_info", diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs index a33ab86..91f2d0e 100644 --- a/src-tauri/src/services/graphic_service.rs +++ b/src-tauri/src/services/graphic_service.rs @@ -1,6 +1,8 @@ +use crate::utils::{self, unit}; use crate::{log_debug, log_error, log_info, log_internal, log_warn}; use nvapi; use nvapi::UtilizationDomain; +use serde::Serialize; use tokio::task::spawn_blocking; use tokio::task::JoinError; @@ -69,3 +71,101 @@ pub async fn get_nvidia_gpu_usage() -> Result { nvapi::Status::Error })? } + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GraphicInfo { + name: String, + vendor_name: String, + clock: u64, + memory_size: String, + memory_size_dedicated: String, +} + +/// +/// GPU情報を取得する +/// +/// - [TODO] AMD GPU の情報も取得する +/// +pub async fn get_nvidia_gpu_info() -> Result, String> { + let handle = spawn_blocking(|| { + log_debug!("start", "get_nvidia_gpu_info", None::<&str>); + + let gpus = match nvapi::PhysicalGpu::enumerate() { + Ok(gpus) => gpus, + Err(e) => { + log_error!( + "enumerate_failed", + "get_nvidia_gpu_info", + Some(e.to_string()) + ); + return Err(e.to_string()); + } + }; + + if gpus.is_empty() { + log_warn!("not found", "get_nvidia_gpu_info", Some("gpu is not found")); + tracing::warn!("gpu is not found"); + } + + let mut gpu_info_list = Vec::new(); + + for gpu in gpus.iter() { + let name = gpu.full_name().unwrap_or("Unknown".to_string()); + + // クロック周波数 (MHz) の取得 + let clock_frequencies = + match gpu.clock_frequencies(nvapi::ClockFrequencyType::Current) { + Ok(freqs) => freqs, + Err(e) => { + log_error!("clock_failed", "get_nvidia_gpu_info", Some(e.to_string())); + continue; + } + }; + + let frequency = match clock_frequencies.get(&nvapi::ClockDomain::Graphics) { + Some(&nvapi::Kilohertz(freq)) => freq as u64, + None => { + log_warn!( + "clock_not_found", + "get_nvidia_gpu_info", + Some("Graphics clock not found") + ); + 0 // デフォルト値として 0 を設定 + } + }; + + // メモリサイズ (MB) の取得 + let memory_info = match gpu.memory_info() { + Ok(info) => info, + Err(e) => { + log_error!( + "memory_info_failed", + "get_nvidia_gpu_info", + Some(e.to_string()) + ); + continue; + } + }; + + let gpu_info = GraphicInfo { + name, + vendor_name: "NVIDIA".to_string(), + clock: frequency, + memory_size: memory_info.shared.to_string(), + memory_size_dedicated: memory_info.dedicated.to_string(), + }; + + gpu_info_list.push(gpu_info); + } + + log_debug!("end", "get_nvidia_gpu_info", None::<&str>); + + Ok(gpu_info_list) + }); + + handle.await.map_err(|e: JoinError| { + log_error!("join_error", "get_nvidia_gpu_info", Some(e.to_string())); + nvapi::Status::Error.to_string() + })? +} diff --git a/src/App.tsx b/src/App.tsx index cd0c942..924ab4c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,7 +20,8 @@ const Page = () => { useSettingsModalListener(); useErrorModalListener(); - useHardwareInfoAtom(); + const { hardwareInfo } = useHardwareInfoAtom(); + console.log(hardwareInfo); const handleShowData = () => { setButtonState(buttonState === "raw" ? "chart" : "raw"); diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index 637552f..809b221 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -6,7 +6,7 @@ import { useEffect } from "react"; const hardInfoAtom = atom(); export const useHardwareInfoAtom = () => { - const [hardware, setHardInfo] = useAtom(hardInfoAtom); + const [hardwareInfo, setHardInfo] = useAtom(hardInfoAtom); useEffect(() => { const init = async () => { @@ -19,12 +19,10 @@ export const useHardwareInfoAtom = () => { }; // データがなければ取得して更新 - if (!hardware) { + if (!hardwareInfo) { init(); } + }, [setHardInfo, hardwareInfo]); - console.log(hardware); - }, [setHardInfo, hardware]); - - return { hardware }; + return { hardwareInfo }; }; diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index 796b970..79646c0 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -6,7 +6,6 @@ export type CpuInfo = { clockUnit: string; vendor: string; coreCount: number; - frequency: number; cpuName: string; }; @@ -18,7 +17,16 @@ export type MemoryInfo = { memoryType: string; }; +export type GraphicInfo = { + clock: number; + name: string; + vendorName: string; + memorySize: string; + memorySizeDedicated: string; +}; + export type HardwareInfo = { cpu: CpuInfo; memory: MemoryInfo; + gpus: GraphicInfo[]; }; From 8edbab685184b8aca154580737822eb9c214b353 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 03:40:13 +0900 Subject: [PATCH 19/43] refactor: change property name --- src-tauri/src/services/system_info_service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/services/system_info_service.rs b/src-tauri/src/services/system_info_service.rs index 8b5b18d..3e37172 100644 --- a/src-tauri/src/services/system_info_service.rs +++ b/src-tauri/src/services/system_info_service.rs @@ -14,7 +14,7 @@ pub struct CpuInfo { name: String, vendor: String, core_count: usize, - frequency: u64, + clock: u64, cpu_name: String, } @@ -33,7 +33,7 @@ pub fn get_cpu_info(system: MutexGuard<'_, System>) -> Result { name: cpus[0].brand().to_string(), vendor: cpus[0].vendor_id().to_string(), core_count: cpus.len(), - frequency: cpus[0].frequency(), + clock: cpus[0].frequency(), cpu_name: cpus[0].name().to_string(), }; From 8f827326e860ff08988b53682f1fbf79d6be8831 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 19:03:23 +0900 Subject: [PATCH 20/43] update: settings.json --- .vscode/settings.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e9699e2..59e5cea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,9 +13,6 @@ "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" }, - "[svelte]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, "editor.codeActionsOnSave": { "quickfix.biome": "explicit", "source.organizeImports.biome": "explicit" @@ -25,9 +22,5 @@ "editor.formatOnSave": true, "editor.inlayHints.enabled": "off" }, - "cSpell.words": [ - "consts", - "nvapi", - "tauri" - ] + "cSpell.words": ["consts", "nvapi", "tauri"] } From 1e3db81374ff29ed3a1aecfaa0efbff89561cc44 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 19:46:19 +0900 Subject: [PATCH 21/43] =?UTF-8?q?update:=20=E3=83=87=E3=82=B6=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- biome.json | 29 ++++++++++++++------------ src/atom/useHardwareInfoAtom.ts | 2 +- src/components/charts/CustomLegend.tsx | 7 ++----- src/components/charts/LineChart.tsx | 12 +++++------ src/template/Chart.tsx | 2 +- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/biome.json b/biome.json index edbde09..83aad5d 100644 --- a/biome.json +++ b/biome.json @@ -1,16 +1,19 @@ { - "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "formatter": { - "indentStyle": "space", + "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "nursery": { + "useSortedClasses": "off" + } + } + }, + "formatter": { + "indentStyle": "space", "indentWidth": 2 - } + } } diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index 809b221..a8556ab 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -3,7 +3,7 @@ import type { HardwareInfo } from "@/types/hardwareDataType"; import { atom, useAtom } from "jotai"; import { useEffect } from "react"; -const hardInfoAtom = atom(); +const hardInfoAtom = atom(); export const useHardwareInfoAtom = () => { const [hardwareInfo, setHardInfo] = useAtom(hardInfoAtom); diff --git a/src/components/charts/CustomLegend.tsx b/src/components/charts/CustomLegend.tsx index a91d95a..3645724 100644 --- a/src/components/charts/CustomLegend.tsx +++ b/src/components/charts/CustomLegend.tsx @@ -11,12 +11,9 @@ const CustomLegend = ({ }) => { return (
-
+
{item.icon} - {item.label} + {item.label}
); diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index 18711ba..f222aaa 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -45,7 +45,7 @@ const LineChart = ({ scales: { x: { display: false }, y: { - display: true, + display: false, suggestedMin: 0, suggestedMax: 100, grid: { color: "rgba(255, 255, 255, 0.2)" }, @@ -107,21 +107,21 @@ const LineChart = ({ const legendItems: Record = { cpu: { - label: "CPU Usage", + label: "CPU", icon: ( ), datasetIndex: 0, }, memory: { - label: "Memory Usage", + label: "Memory", icon: ( ), datasetIndex: 1, }, gpu: { - label: "GPU Usage", + label: "GPU", icon: ( +
-
+
diff --git a/src/template/Chart.tsx b/src/template/Chart.tsx index c49192f..7810d40 100644 --- a/src/template/Chart.tsx +++ b/src/template/Chart.tsx @@ -57,7 +57,7 @@ const ChartTemplate = () => { ); }, [settings]); - return
{renderedCharts}
; + return
{renderedCharts}
; }; export default ChartTemplate; From 9eb41e19326f1383ba3716cc260f6bd71c141d50 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 19:47:50 +0900 Subject: [PATCH 22/43] =?UTF-8?q?refactor:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 15 +-------- src/components/Sample.tsx | 66 --------------------------------------- 2 files changed, 1 insertion(+), 80 deletions(-) delete mode 100644 src/components/Sample.tsx diff --git a/src/App.tsx b/src/App.tsx index 924ab4c..baa5b1a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from "react"; -import TestTemplate from "./components/Sample"; import ChartTemplate from "./template/Chart"; import "./index.css"; import { @@ -11,10 +10,7 @@ import { useHardwareInfoAtom } from "./atom/useHardwareInfoAtom"; import { useSettingsAtom } from "./atom/useSettingsAtom"; import { useDarkMode } from "./hooks/useDarkMode"; -type ButtonState = "chart" | "raw"; - const Page = () => { - const [buttonState, setButtonState] = useState("chart"); const { settings } = useSettingsAtom(); const { toggle } = useDarkMode(); @@ -23,10 +19,6 @@ const Page = () => { const { hardwareInfo } = useHardwareInfoAtom(); console.log(hardwareInfo); - const handleShowData = () => { - setButtonState(buttonState === "raw" ? "chart" : "raw"); - }; - useEffect(() => { if (settings?.theme) { toggle(settings.theme === "dark"); @@ -36,12 +28,7 @@ const Page = () => { return (

Hardware Monitor Proto

-
- -
- {buttonState === "raw" ? : } +
); diff --git a/src/components/Sample.tsx b/src/components/Sample.tsx deleted file mode 100644 index 8377bb2..0000000 --- a/src/components/Sample.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { - getCpuMemoryHistory, - getCpuUsage, - getCpuUsageHistory, - getGpuUsage, - getGpuUsageHistory, - getMemoryUsage, -} from "@/services/hardwareService"; -import { useEffect, useState } from "react"; - -const Sample = () => { - const [cpuUsage, setCpuUsage] = useState(0); - const [memoryUsage, setMemoryUsage] = useState(0); - const [gpuUsage, setGpuUsage] = useState(0); - - const [cpuHistory, setCpuHistory] = useState([]); - const [memoryHistory, setMemoryHistory] = useState([]); - const [gpuHistory, setGpuHistory] = useState([]); - - useEffect(() => { - const interval = setInterval(async () => { - setCpuUsage(await getCpuUsage()); - setMemoryUsage(await getMemoryUsage()); - setCpuHistory(await getCpuUsageHistory(30)); - setMemoryHistory(await getCpuMemoryHistory(30)); - setGpuUsage(await getGpuUsage()); - setGpuHistory(await getGpuUsageHistory(30)); - }, 1000); - - return () => clearInterval(interval); - }, []); - - return ( -
-

CPU: {cpuUsage}%

-

MEMORY: {memoryUsage}%

-

GPU: {gpuUsage}%

- -
-

CPU History

-

Count: {cpuHistory.length}

-
    - {cpuHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-

MEMORY History

-

Count: {memoryHistory.length}

-
    - {memoryHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-

GPU History

-

Count: {gpuHistory.length}

-
    - {gpuHistory.map((item, index) => ( -
  • {item}
  • - ))} -
-
-
- ); -}; - -export default Sample; From 4568db8e85bb5fed74308bace23e8b70a9999e5c Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 15 Sep 2024 20:48:10 +0900 Subject: [PATCH 23/43] refactor: delete unused code --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index baa5b1a..431ba42 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import ChartTemplate from "./template/Chart"; import "./index.css"; import { From cceb2c3c29719db74dae2f605ae075cdd9d41305 Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 16 Sep 2024 00:13:31 +0900 Subject: [PATCH 24/43] =?UTF-8?q?change:=20=E5=87=A1=E4=BE=8B=E3=82=92bord?= =?UTF-8?q?er=E5=A4=96=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/charts/LineChart.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/charts/LineChart.tsx b/src/components/charts/LineChart.tsx index f222aaa..5ed3acf 100644 --- a/src/components/charts/LineChart.tsx +++ b/src/components/charts/LineChart.tsx @@ -134,8 +134,13 @@ const LineChart = ({ }; return ( -
- +
+
From 809bcb1e36478fba06013f7b4f9cc36cfe0da125 Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 16 Sep 2024 02:01:59 +0900 Subject: [PATCH 25/43] feature: side menu --- .vscode/settings.json | 5 +- src/App.tsx | 15 +++++- src/atom/ui.ts | 3 ++ src/template/SideMenu.tsx | 99 +++++++++++++++++++++++++++++++++++++++ src/types/ui.ts | 1 + 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 src/template/SideMenu.tsx create mode 100644 src/types/ui.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 59e5cea..511a1c7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,5 +22,8 @@ "editor.formatOnSave": true, "editor.inlayHints.enabled": "off" }, - "cSpell.words": ["consts", "nvapi", "tauri"] + "cSpell.words": ["consts", "nvapi", "tauri"], + "tailwindCSS.experimental.classRegex": [ + "tv\\(([^)(]*(?:\\([^)(]*(?:\\([^)(]*(?:\\([^)(]*\\)[^)(]*)*\\)[^)(]*)*\\)[^)(]*)*)\\)" + ] } diff --git a/src/App.tsx b/src/App.tsx index 431ba42..211df31 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,12 +6,17 @@ import { useSettingsModalListener, } from "@/hooks/useTauriEventListener"; import SettingsSheet from "@/template/SettingsSheet"; +import { useAtom } from "jotai"; +import { selectedMenuAtom } from "./atom/ui"; import { useHardwareInfoAtom } from "./atom/useHardwareInfoAtom"; import { useSettingsAtom } from "./atom/useSettingsAtom"; import { useDarkMode } from "./hooks/useDarkMode"; +import SideMenu from "./template/SideMenu"; +import type { SelectedMenuType } from "./types/ui"; const Page = () => { const { settings } = useSettingsAtom(); + const [selectedMenu] = useAtom(selectedMenuAtom); const { toggle } = useDarkMode(); useSettingsModalListener(); @@ -25,10 +30,16 @@ const Page = () => { } }, [settings?.theme, toggle]); + const displayTargets: Record = { + dashboard:
TODO
, + usage: , + settings: , + }; + return (
-

Hardware Monitor Proto

- + + {displayTargets[selectedMenu]}
); diff --git a/src/atom/ui.ts b/src/atom/ui.ts index d6cf2d9..ff81c0b 100644 --- a/src/atom/ui.ts +++ b/src/atom/ui.ts @@ -1,5 +1,8 @@ +import type { SelectedMenuType } from "@/types/ui"; import { atom } from "jotai"; export const modalAtoms = { showSettingsModal: atom(false), }; + +export const selectedMenuAtom = atom("usage"); // [TODO] change to dashboard diff --git a/src/template/SideMenu.tsx b/src/template/SideMenu.tsx new file mode 100644 index 0000000..a2b7310 --- /dev/null +++ b/src/template/SideMenu.tsx @@ -0,0 +1,99 @@ +import { selectedMenuAtom } from "@/atom/ui"; +import type { SelectedMenuType } from "@/types/ui"; +import { CaretDoubleLeft, CaretDoubleRight } from "@phosphor-icons/react"; +import { useAtom } from "jotai"; +import { useState } from "react"; +import { memo, useCallback } from "react"; +import { tv } from "tailwind-variants"; + +const buttonClasses = tv({ + base: "fixed top-0 rounded-xl hover:bg-gray-700 p-2 transition-all", + variants: { + open: { + true: "left-64", + false: "left-0", + }, + }, +}); + +const sideMenuClasses = tv({ + base: "fixed top-0 left-0 h-full bg-gray-800 text-white w-64 transform transition-transform duration-300 ease-in-out", + variants: { + open: { + true: "translate-x-0", + false: "-translate-x-full", + }, + }, +}); + +const menuItemClasses = tv({ + base: "mb-2 rounded-lg transition-colors", + variants: { + selected: { + true: "text-white font-bold", + false: "text-slate-500 hover:text-slate-200", + }, + }, +}); + +const menuTitles: Record = { + dashboard: "PC Monitor", + usage: "Usage", + settings: "Settings", +}; + +const SideMenu = () => { + const [isOpen, setIsOpen] = useState(false); + const [selectedMenu, setSelectedMenu] = useAtom(selectedMenuAtom); + + const toggleMenu = useCallback(() => { + setIsOpen((prev) => !prev); + }, []); + + const handleMenuClick = useCallback( + (type: SelectedMenuType) => { + setSelectedMenu(type); + }, + [setSelectedMenu], + ); + + const MenuItem = memo(({ type }: { type: SelectedMenuType }) => { + return ( +
  • + +
  • + ); + }); + + return ( +
    + +
    +
      +
    • +

      Hardware Monitor

      +
    • + + + +
    +
    +
    + ); +}; + +export default SideMenu; diff --git a/src/types/ui.ts b/src/types/ui.ts new file mode 100644 index 0000000..44ca148 --- /dev/null +++ b/src/types/ui.ts @@ -0,0 +1 @@ +export type SelectedMenuType = "dashboard" | "usage" | "settings"; From e2b60d0f12ce575fed5327172d7efdc8af727cc6 Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 16 Sep 2024 02:04:58 +0900 Subject: [PATCH 26/43] =?UTF-8?q?update:=20UI=E5=BE=AE=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 2 +- src/template/SideMenu.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 211df31..78966bd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,7 +33,7 @@ const Page = () => { const displayTargets: Record = { dashboard:
    TODO
    , usage: , - settings: , + settings:
    TODO
    , }; return ( diff --git a/src/template/SideMenu.tsx b/src/template/SideMenu.tsx index a2b7310..0839245 100644 --- a/src/template/SideMenu.tsx +++ b/src/template/SideMenu.tsx @@ -31,13 +31,13 @@ const menuItemClasses = tv({ variants: { selected: { true: "text-white font-bold", - false: "text-slate-500 hover:text-slate-200", + false: "text-slate-400 hover:text-slate-100", }, }, }); const menuTitles: Record = { - dashboard: "PC Monitor", + dashboard: "Dashboard", usage: "Usage", settings: "Settings", }; From 64aa1f4fe2a1af381c25af9ef1b98ab4cb941f58 Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 22 Sep 2024 18:15:14 +0900 Subject: [PATCH 27/43] refactor: rename `Usage.tsx` --- src/App.tsx | 2 +- src/template/{Chart.tsx => Usage.tsx} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/template/{Chart.tsx => Usage.tsx} (100%) diff --git a/src/App.tsx b/src/App.tsx index 78966bd..75b5b43 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import ChartTemplate from "./template/Chart"; +import ChartTemplate from "./template/Usage"; import "./index.css"; import { useErrorModalListener, diff --git a/src/template/Chart.tsx b/src/template/Usage.tsx similarity index 100% rename from src/template/Chart.tsx rename to src/template/Usage.tsx From 2e8e7e20525fc79ec946959818112bed714ba9bd Mon Sep 17 00:00:00 2001 From: shm Date: Sun, 22 Sep 2024 18:23:18 +0900 Subject: [PATCH 28/43] feature: add `DoughnutChart` --- src/App.tsx | 3 +- src/components/charts/DoughnutChart.tsx | 54 +++++++++++++++++++++++++ src/consts/chart.ts | 14 +++++++ src/template/Dashboard.tsx | 11 +++++ src/types/hardwareDataType.ts | 2 + 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 src/components/charts/DoughnutChart.tsx create mode 100644 src/template/Dashboard.tsx diff --git a/src/App.tsx b/src/App.tsx index 75b5b43..5a5df81 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import Dashboard from "./template/Dashboard"; import ChartTemplate from "./template/Usage"; import "./index.css"; import { @@ -31,7 +32,7 @@ const Page = () => { }, [settings?.theme, toggle]); const displayTargets: Record = { - dashboard:
    TODO
    , + dashboard: , usage: , settings:
    TODO
    , }; diff --git a/src/components/charts/DoughnutChart.tsx b/src/components/charts/DoughnutChart.tsx new file mode 100644 index 0000000..963cc43 --- /dev/null +++ b/src/components/charts/DoughnutChart.tsx @@ -0,0 +1,54 @@ +import { displayDataType, displayHardType } from "@/consts/chart"; +import type { ChartDataType, HardwareDataType } from "@/types/hardwareDataType"; +import { + ArcElement, + Chart as ChartJS, + type ChartOptions, + Legend, + Tooltip, +} from "chart.js"; +import { Doughnut } from "react-chartjs-2"; + +ChartJS.register(ArcElement, Tooltip, Legend); + +const DoughnutChart = ({ + chartData, + hardType, + dataType, +}: { + chartData: number; + hardType: ChartDataType; + dataType: HardwareDataType; +}) => { + const data = { + datasets: [ + { + data: [chartData, 100 - chartData], + backgroundColor: ["#888", "#222"], + borderWidth: 0, + }, + ], + }; + + const options: ChartOptions<"doughnut"> = { + cutout: "85%", + plugins: { + tooltip: { enabled: false }, + }, + }; + + return ( +
    +

    {displayHardType[hardType]}

    + +
    + {chartData}% +
    + + {displayDataType[dataType]} + +
    + ); +}; + +export default DoughnutChart; diff --git a/src/consts/chart.ts b/src/consts/chart.ts index f5a4845..33d1c28 100644 --- a/src/consts/chart.ts +++ b/src/consts/chart.ts @@ -1,6 +1,20 @@ +import type { ChartDataType, HardwareDataType } from "@/types/hardwareDataType"; + export const chartConfig = { /** * グラフの履歴の長さ(秒) */ historyLengthSec: 60, } as const; + +export const displayHardType: Record = { + cpu: "CPU", + memory: "RAM", + gpu: "GPU", +} as const; + +export const displayDataType: Record = { + temp: "Temperature", + usage: "Usage", + clock: "Clock", +} as const; diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx new file mode 100644 index 0000000..d9b86f5 --- /dev/null +++ b/src/template/Dashboard.tsx @@ -0,0 +1,11 @@ +import DoughnutChart from "@/components/charts/DoughnutChart"; + +const Dashboard = () => { + return ( +
    + +
    + ); +}; + +export default Dashboard; diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index 79646c0..4dc6723 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -1,5 +1,7 @@ export type ChartDataType = "cpu" | "memory" | "gpu"; +export type HardwareDataType = "temp" | "usage" | "clock"; + export type CpuInfo = { name: string; clock: number; From 61ffd406c031c7016bd5e8fea4e07c55485e510a Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 23 Sep 2024 03:16:11 +0900 Subject: [PATCH 29/43] feature: add Dashboard --- src-tauri/src/services/system_info_service.rs | 2 + src/App.tsx | 9 +- src/atom/useHardwareInfoAtom.ts | 10 +- src/components/charts/DoughnutChart.tsx | 2 +- src/template/Dashboard.tsx | 130 +++++++++++++++++- src/template/SideMenu.tsx | 2 +- src/template/Usage.tsx | 5 - src/types/hardwareDataType.ts | 7 +- src/types/tauriType.ts | 0 9 files changed, 149 insertions(+), 18 deletions(-) delete mode 100644 src/types/tauriType.ts diff --git a/src-tauri/src/services/system_info_service.rs b/src-tauri/src/services/system_info_service.rs index 3e37172..e15819d 100644 --- a/src-tauri/src/services/system_info_service.rs +++ b/src-tauri/src/services/system_info_service.rs @@ -15,6 +15,7 @@ pub struct CpuInfo { vendor: String, core_count: usize, clock: u64, + clock_unit: String, cpu_name: String, } @@ -34,6 +35,7 @@ pub fn get_cpu_info(system: MutexGuard<'_, System>) -> Result { vendor: cpus[0].vendor_id().to_string(), core_count: cpus.len(), clock: cpus[0].frequency(), + clock_unit: "MHz".to_string(), cpu_name: cpus[0].name().to_string(), }; diff --git a/src/App.tsx b/src/App.tsx index 5a5df81..775dc14 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { useEffect } from "react"; import Dashboard from "./template/Dashboard"; import ChartTemplate from "./template/Usage"; import "./index.css"; +import { useUsageUpdater } from "@/hooks/useHardwareData"; import { useErrorModalListener, useSettingsModalListener, @@ -9,7 +10,6 @@ import { import SettingsSheet from "@/template/SettingsSheet"; import { useAtom } from "jotai"; import { selectedMenuAtom } from "./atom/ui"; -import { useHardwareInfoAtom } from "./atom/useHardwareInfoAtom"; import { useSettingsAtom } from "./atom/useSettingsAtom"; import { useDarkMode } from "./hooks/useDarkMode"; import SideMenu from "./template/SideMenu"; @@ -22,8 +22,9 @@ const Page = () => { useSettingsModalListener(); useErrorModalListener(); - const { hardwareInfo } = useHardwareInfoAtom(); - console.log(hardwareInfo); + useUsageUpdater("cpu"); + useUsageUpdater("memory"); + useUsageUpdater("gpu"); useEffect(() => { if (settings?.theme) { @@ -38,7 +39,7 @@ const Page = () => { }; return ( -
    +
    {displayTargets[selectedMenu]} diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index a8556ab..9f2d92c 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -1,9 +1,12 @@ import { getHardwareInfo } from "@/services/hardwareService"; import type { HardwareInfo } from "@/types/hardwareDataType"; +import { CopySimple } from "@phosphor-icons/react"; import { atom, useAtom } from "jotai"; import { useEffect } from "react"; -const hardInfoAtom = atom(); +const hardInfoAtom = atom({ + isFetched: false, +}); export const useHardwareInfoAtom = () => { const [hardwareInfo, setHardInfo] = useAtom(hardInfoAtom); @@ -13,13 +16,16 @@ export const useHardwareInfoAtom = () => { try { const hardwareInfo = await getHardwareInfo(); setHardInfo(hardwareInfo); + + hardwareInfo.isFetched = true; } catch (e) { console.error(e); + hardwareInfo.isFetched = false; } }; // データがなければ取得して更新 - if (!hardwareInfo) { + if (!hardwareInfo.isFetched) { init(); } }, [setHardInfo, hardwareInfo]); diff --git a/src/components/charts/DoughnutChart.tsx b/src/components/charts/DoughnutChart.tsx index 963cc43..0e42140 100644 --- a/src/components/charts/DoughnutChart.tsx +++ b/src/components/charts/DoughnutChart.tsx @@ -38,7 +38,7 @@ const DoughnutChart = ({ }; return ( -
    +

    {displayHardType[hardType]}

    diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index d9b86f5..0c53935 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -1,9 +1,135 @@ +import { + cpuUsageHistoryAtom, + graphicUsageHistoryAtom, + memoryUsageHistoryAtom, +} from "@/atom/chart"; +import { useHardwareInfoAtom } from "@/atom/useHardwareInfoAtom"; import DoughnutChart from "@/components/charts/DoughnutChart"; +import { useAtom } from "jotai"; + +const InfoTable = ({ + title, + data, +}: { + title?: string; + data: { [key: string]: string | number }; +}) => { + return ( +
    + {title &&

    {title}

    } + + + {Object.keys(data).map((key) => ( + + + + + ))} + +
    {key}{data[key]}
    +
    + ); +}; + +const DataArea = ({ children }: { children: React.ReactNode }) => { + return ( +
    +
    +
    {children}
    +
    +
    + ); +}; + +const CPUInfo = () => { + const [cpuUsageHistory] = useAtom(cpuUsageHistoryAtom); + const { hardwareInfo } = useHardwareInfoAtom(); + + return ( + hardwareInfo.cpu && ( + <> + + + + ) + ); +}; + +const GPUInfo = () => { + const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); + const { hardwareInfo } = useHardwareInfoAtom(); + + return ( + hardwareInfo.gpus && ( + <> + + + + ) + ); +}; + +const MemoryInfo = () => { + const [memoryUsageHistory] = useAtom(memoryUsageHistoryAtom); + const { hardwareInfo } = useHardwareInfoAtom(); + + return ( + hardwareInfo.memory && ( + <> + + + + ) + ); +}; const Dashboard = () => { return ( -
    - +
    +
    + + + + + + +
    + + + +
    ); }; diff --git a/src/template/SideMenu.tsx b/src/template/SideMenu.tsx index 0839245..9987ab8 100644 --- a/src/template/SideMenu.tsx +++ b/src/template/SideMenu.tsx @@ -74,7 +74,7 @@ const SideMenu = () => { }); return ( -
    +
    diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index 0c53935..d821662 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -107,7 +107,8 @@ const MemoryInfo = () => { data={{ "Memory Type": hardwareInfo.memory.memoryType, "Total Memory": hardwareInfo.memory.size, - "Memory Count": hardwareInfo.memory.memoryCount, + "Memory Count": `${hardwareInfo.memory.memoryCount}/${hardwareInfo.memory.totalSlots}`, + "Memory Clock": `${hardwareInfo.memory.clock} ${hardwareInfo.memory.clockUnit}`, }} /> diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index 6dfd0b1..47058e5 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -16,6 +16,7 @@ export type MemoryInfo = { clock: number; clockUnit: string; memoryCount: number; + totalSlots: number; memoryType: string; }; From 41c7018ae3271d9a59316f89f6a5f0b4c50e5f1e Mon Sep 17 00:00:00 2001 From: shm Date: Mon, 23 Sep 2024 18:55:46 +0900 Subject: [PATCH 38/43] =?UTF-8?q?fix:=20logging=20=E3=81=A7=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=8C=E8=90=BD=E3=81=A1=E3=82=8B=E7=AE=87=E6=89=80?= =?UTF-8?q?=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/services/system_info_service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-tauri/src/services/system_info_service.rs b/src-tauri/src/services/system_info_service.rs index 287f618..3a73517 100644 --- a/src-tauri/src/services/system_info_service.rs +++ b/src-tauri/src/services/system_info_service.rs @@ -191,13 +191,13 @@ where // WMIクエリを実行してメモリ情報を取得 let results: Vec = wmi_con - .raw_query(query) + .raw_query(query.clone()) .map_err(|e| format!("Failed to execute query: {:?}", e))?; log_info!( &format!("mem info: {:?}", results), "get_memory_info_in_thread", - &format!("query: {}", query) + Some(&format!("query: {}", query)) ); Ok(results) From 03cbe1da96193b4e148c7f0b61947757716aa8f9 Mon Sep 17 00:00:00 2001 From: shm Date: Tue, 24 Sep 2024 01:51:25 +0900 Subject: [PATCH 39/43] =?UTF-8?q?feature:=20GPU=E6=B8=A9=E5=BA=A6=E3=81=AE?= =?UTF-8?q?=E5=8F=96=E5=BE=97=EF=BC=88Nvidia=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/hardware.rs | 12 +++++ src-tauri/src/main.rs | 1 + src-tauri/src/services/graphic_service.rs | 58 +++++++++++++++++++++++ src/App.tsx | 6 ++- src/atom/chart.ts | 3 ++ src/components/charts/DoughnutChart.tsx | 21 +++++++- src/hooks/useHardwareData.ts | 56 +++++++++++++++++++--- src/services/hardwareService.ts | 6 ++- src/template/Dashboard.tsx | 30 ++++++++++-- src/types/hardwareDataType.ts | 7 ++- 10 files changed, 184 insertions(+), 16 deletions(-) diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 6427c01..1030934 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -105,6 +105,18 @@ pub async fn get_gpu_usage() -> Result { } } +/// +/// ## GPU温度を取得 +/// +#[command] +pub async fn get_gpu_temperature() -> Result, String> +{ + match graphic_service::get_nvidia_gpu_temperature().await { + Ok(temps) => Ok(temps), + Err(e) => Err(format!("Failed to get GPU temperature: {:?}", e)), + } +} + /// /// ## CPU使用率の履歴を取得 /// diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 53c7c2f..ed1b721 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -54,6 +54,7 @@ fn main() { hardware::get_hardware_info, hardware::get_memory_usage, hardware::get_gpu_usage, + hardware::get_gpu_temperature, hardware::get_cpu_usage_history, hardware::get_memory_usage_history, hardware::get_gpu_usage_history, diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs index 16d5432..1c787e2 100644 --- a/src-tauri/src/services/graphic_service.rs +++ b/src-tauri/src/services/graphic_service.rs @@ -72,6 +72,64 @@ pub async fn get_nvidia_gpu_usage() -> Result { })? } +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GpuTemperature { + name: String, + value: f64, // 摂氏温度 +} + +/// +/// ## GPU温度を取得する(NVAPI を使用) +/// +pub async fn get_nvidia_gpu_temperature() -> Result, nvapi::Status> { + let handle = spawn_blocking(|| { + log_debug!("start", "get_nvidia_gpu_temperature", None::<&str>); + + let gpus = nvapi::PhysicalGpu::enumerate()?; + + if gpus.is_empty() { + log_warn!( + "not found", + "get_nvidia_gpu_temperature", + Some("gpu is not found") + ); + tracing::warn!("gpu is not found"); + return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す + } + + let mut temperatures = Vec::new(); + + for gpu in gpus.iter() { + // 温度情報を取得 + let thermal_settings = gpu.thermal_settings(None).map_err(|e| { + log_warn!( + "thermal_settings_failed", + "get_nvidia_gpu_temperature", + Some(&format!("{:?}", e)) + ); + nvapi::Status::Error + })?; + + temperatures.push(GpuTemperature { + name: gpu.full_name().unwrap_or("Unknown".to_string()), + value: thermal_settings[0].current_temperature.0 as f64, // thermal_settings の0番目の温度を f64 に変換 + }); + } + + Ok(temperatures) + }); + + handle.await.map_err(|e: JoinError| { + log_error!( + "join_error", + "get_nvidia_gpu_temperature", + Some(e.to_string()) + ); + nvapi::Status::Error + })? +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct GraphicInfo { diff --git a/src/App.tsx b/src/App.tsx index 775dc14..329054b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,10 @@ import { useEffect } from "react"; import Dashboard from "./template/Dashboard"; import ChartTemplate from "./template/Usage"; import "./index.css"; -import { useUsageUpdater } from "@/hooks/useHardwareData"; +import { + useHardwareTempUpdater, + useUsageUpdater, +} from "@/hooks/useHardwareData"; import { useErrorModalListener, useSettingsModalListener, @@ -25,6 +28,7 @@ const Page = () => { useUsageUpdater("cpu"); useUsageUpdater("memory"); useUsageUpdater("gpu"); + useHardwareTempUpdater("gpu"); useEffect(() => { if (settings?.theme) { diff --git a/src/atom/chart.ts b/src/atom/chart.ts index 6b2128f..196b6a8 100644 --- a/src/atom/chart.ts +++ b/src/atom/chart.ts @@ -1,5 +1,8 @@ +import type { Temperatures } from "@/types/hardwareDataType"; import { atom } from "jotai"; export const cpuUsageHistoryAtom = atom([]); export const memoryUsageHistoryAtom = atom([]); export const graphicUsageHistoryAtom = atom([]); +export const cpuTempAtom = atom([]); +export const gpuTempAtom = atom([]); diff --git a/src/components/charts/DoughnutChart.tsx b/src/components/charts/DoughnutChart.tsx index 6754802..0f0fcb1 100644 --- a/src/components/charts/DoughnutChart.tsx +++ b/src/components/charts/DoughnutChart.tsx @@ -16,10 +16,12 @@ const DoughnutChart = ({ chartData, hardType, dataType, + showTitle, }: { chartData: number; hardType: ChartDataType; dataType: HardwareDataType; + showTitle: boolean; }) => { const data = { datasets: [ @@ -44,12 +46,27 @@ const DoughnutChart = ({ clock: , }; + const dataTypeUnits: Record = { + usage: "%", + temp: "℃", + clock: "MHz", + }; + return (
    -

    {displayHardType[hardType]}

    +

    + { + showTitle + ? displayHardType[hardType] + : " " /** [TODO] タイトルはコンポーネント外のほうが使いやすそう */ + } +

    - {chartData}% + + {chartData} + {dataTypeUnits[dataType]} +
    {dataTypeIcons[dataType]} diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts index 8323b71..7215ddc 100644 --- a/src/hooks/useHardwareData.ts +++ b/src/hooks/useHardwareData.ts @@ -1,27 +1,30 @@ import { + cpuTempAtom, cpuUsageHistoryAtom, + gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; import { chartConfig } from "@/consts/chart"; import { getCpuUsage, + getGpuTemperature, getGpuUsage, getMemoryUsage, } from "@/services/hardwareService"; -import type { ChartDataType } from "@/types/hardwareDataType"; +import type { ChartDataType, Temperatures } from "@/types/hardwareDataType"; import { type PrimitiveAtom, useSetAtom } from "jotai"; import { useEffect } from "react"; -type AtomActionMapping = { - atom: PrimitiveAtom; - action: () => Promise; -}; - /** * ハードウェア使用率の履歴を更新する */ export const useUsageUpdater = (dataType: ChartDataType) => { + type AtomActionMapping = { + atom: PrimitiveAtom; + action: () => Promise; + }; + const mapping: Record = { cpu: { atom: cpuUsageHistoryAtom, @@ -59,3 +62,44 @@ export const useUsageUpdater = (dataType: ChartDataType) => { return () => clearInterval(intervalId); }, [setHistory, getUsage]); }; + +export const useHardwareTempUpdater = ( + dataType: Exclude, +) => { + type AtomActionMapping = { + atom: PrimitiveAtom; + action: () => Promise; + }; + + const mapping: Record, AtomActionMapping> = { + cpu: { + atom: cpuTempAtom, + action: () => { + console.error("Not implemented"); + return Promise.resolve([]); + }, + }, + gpu: { + atom: gpuTempAtom, + action: getGpuTemperature, + }, + }; + + const setData = useSetAtom(mapping[dataType].atom); + const getTemp = mapping[dataType].action; + + useEffect(() => { + const fetchData = async () => { + const temp = await getTemp(); + setData(temp); + }; + + fetchData(); + + const intervalId = setInterval(async () => { + fetchData; + }, 10000); + + return () => clearInterval(intervalId); + }, [setData, getTemp]); +}; diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index 3c13a78..e51e92c 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -1,4 +1,4 @@ -import type { HardwareInfo } from "@/types/hardwareDataType"; +import type { HardwareInfo, Temperatures } from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; export const getCpuUsage = async (): Promise => { @@ -28,3 +28,7 @@ export const getGpuUsage = async (): Promise => { export const getGpuUsageHistory = (seconds: number): Promise => { return invoke("get_gpu_usage_history", { seconds: seconds }); }; + +export const getGpuTemperature = async (): Promise => { + return await invoke("get_gpu_temperature"); +}; diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index d821662..358fd45 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -1,5 +1,6 @@ import { cpuUsageHistoryAtom, + gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; @@ -52,6 +53,7 @@ const CPUInfo = () => { chartData={cpuUsageHistory[cpuUsageHistory.length - 1]} dataType={"usage"} hardType="cpu" + showTitle={true} /> { const GPUInfo = () => { const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); + const [gpuTemp] = useAtom(gpuTempAtom); const { hardwareInfo } = useHardwareInfoAtom(); + const targetTemperature = gpuTemp.find( + (x) => x.name === hardwareInfo.gpus[0].name, + )?.value; + return ( hardwareInfo.gpus && ( <> - +
    + + {targetTemperature && ( + + )} +
    + { chartData={memoryUsageHistory[memoryUsageHistory.length - 1]} dataType={"usage"} hardType="memory" + showTitle={true} /> ; From a45486eb292307cf1481527191ce3173e18be5f7 Mon Sep 17 00:00:00 2001 From: shm Date: Tue, 24 Sep 2024 01:55:27 +0900 Subject: [PATCH 40/43] =?UTF-8?q?fix:=20=E3=82=B3=E3=83=B3=E3=83=91?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=82=A8=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/atom/useHardwareInfoAtom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index 4bf184f..6fb0c4b 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -5,6 +5,7 @@ import { useEffect } from "react"; const hardInfoAtom = atom({ isFetched: false, + gpus: [], }); export const useHardwareInfoAtom = () => { From 598dc9e384e4e002c118af2d5a8e8e163a27e8f9 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 28 Sep 2024 13:56:36 +0900 Subject: [PATCH 41/43] feature: gpu fan speed --- src-tauri/src/commands/hardware.rs | 14 +++++- src-tauri/src/main.rs | 1 + src-tauri/src/services/graphic_service.rs | 61 +++++++++++++++++++++-- src/App.tsx | 8 ++- src/atom/chart.ts | 8 +-- src/atom/useHardwareInfoAtom.ts | 27 +++++----- src/hooks/useHardwareData.ts | 54 ++++++++++++++------ src/services/hardwareService.ts | 12 +++-- src/template/Dashboard.tsx | 24 +++++++-- src/types/hardwareDataType.ts | 4 +- 10 files changed, 162 insertions(+), 51 deletions(-) diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index 1030934..a7a07ff 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -109,14 +109,24 @@ pub async fn get_gpu_usage() -> Result { /// ## GPU温度を取得 /// #[command] -pub async fn get_gpu_temperature() -> Result, String> -{ +pub async fn get_gpu_temperature() -> Result, String> { match graphic_service::get_nvidia_gpu_temperature().await { Ok(temps) => Ok(temps), Err(e) => Err(format!("Failed to get GPU temperature: {:?}", e)), } } +/// +/// ## GPUのファン回転数を取得 +/// +#[command] +pub async fn get_nvidia_gpu_cooler() -> Result, String> { + match graphic_service::get_nvidia_gpu_cooler_stat().await { + Ok(temps) => Ok(temps), + Err(e) => Err(format!("Failed to get GPU cooler status: {:?}", e)), + } +} + /// /// ## CPU使用率の履歴を取得 /// diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ed1b721..bc93e53 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -55,6 +55,7 @@ fn main() { hardware::get_memory_usage, hardware::get_gpu_usage, hardware::get_gpu_temperature, + hardware::get_nvidia_gpu_cooler, hardware::get_cpu_usage_history, hardware::get_memory_usage_history, hardware::get_gpu_usage_history, diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs index 1c787e2..79be04d 100644 --- a/src-tauri/src/services/graphic_service.rs +++ b/src-tauri/src/services/graphic_service.rs @@ -74,7 +74,7 @@ pub async fn get_nvidia_gpu_usage() -> Result { #[derive(Debug, Clone, serde::Serialize)] #[serde(rename_all = "camelCase")] -pub struct GpuTemperature { +pub struct NameValue { name: String, value: f64, // 摂氏温度 } @@ -82,7 +82,7 @@ pub struct GpuTemperature { /// /// ## GPU温度を取得する(NVAPI を使用) /// -pub async fn get_nvidia_gpu_temperature() -> Result, nvapi::Status> { +pub async fn get_nvidia_gpu_temperature() -> Result, nvapi::Status> { let handle = spawn_blocking(|| { log_debug!("start", "get_nvidia_gpu_temperature", None::<&str>); @@ -111,7 +111,7 @@ pub async fn get_nvidia_gpu_temperature() -> Result, nvapi:: nvapi::Status::Error })?; - temperatures.push(GpuTemperature { + temperatures.push(NameValue { name: gpu.full_name().unwrap_or("Unknown".to_string()), value: thermal_settings[0].current_temperature.0 as f64, // thermal_settings の0番目の温度を f64 に変換 }); @@ -130,6 +130,61 @@ pub async fn get_nvidia_gpu_temperature() -> Result, nvapi:: })? } +/// +/// ## GPUのファン回転数を取得する(NVAPI を使用) +/// +pub async fn get_nvidia_gpu_cooler_stat() -> Result, nvapi::Status> { + let handle = spawn_blocking(|| { + log_debug!("start", "get_nvidia_gpu_cooler_stat", None::<&str>); + + let gpus = nvapi::PhysicalGpu::enumerate()?; + + if gpus.is_empty() { + log_warn!( + "not found", + "get_nvidia_gpu_cooler_stat", + Some("gpu is not found") + ); + tracing::warn!("gpu is not found"); + return Err(nvapi::Status::Error); // GPUが見つからない場合はエラーを返す + } + + let mut cooler_infos = Vec::new(); + + print!("{:?}", gpus); + + for gpu in gpus.iter() { + // 温度情報を取得 + let cooler_settings = gpu.cooler_settings(None).map_err(|e| { + log_warn!( + "cooler_settings_failed", + "get_nvidia_gpu_cooler_stat", + Some(&format!("{:?}", e)) + ); + nvapi::Status::Error + })?; + + print!("{:?}", cooler_settings); + + cooler_infos.push(NameValue { + name: gpu.full_name().unwrap_or("Unknown".to_string()), + value: cooler_settings[0].current_level.0 as f64, + }); + } + + Ok(cooler_infos) + }); + + handle.await.map_err(|e: JoinError| { + log_error!( + "join_error", + "get_nvidia_gpu_cooler_stat", + Some(e.to_string()) + ); + nvapi::Status::Error + })? +} + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct GraphicInfo { diff --git a/src/App.tsx b/src/App.tsx index 329054b..1eb0bdd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,10 +2,7 @@ import { useEffect } from "react"; import Dashboard from "./template/Dashboard"; import ChartTemplate from "./template/Usage"; import "./index.css"; -import { - useHardwareTempUpdater, - useUsageUpdater, -} from "@/hooks/useHardwareData"; +import { useHardwareUpdater, useUsageUpdater } from "@/hooks/useHardwareData"; import { useErrorModalListener, useSettingsModalListener, @@ -28,7 +25,8 @@ const Page = () => { useUsageUpdater("cpu"); useUsageUpdater("memory"); useUsageUpdater("gpu"); - useHardwareTempUpdater("gpu"); + useHardwareUpdater("gpu", "temp"); + useHardwareUpdater("gpu", "fan"); useEffect(() => { if (settings?.theme) { diff --git a/src/atom/chart.ts b/src/atom/chart.ts index 196b6a8..9c30400 100644 --- a/src/atom/chart.ts +++ b/src/atom/chart.ts @@ -1,8 +1,10 @@ -import type { Temperatures } from "@/types/hardwareDataType"; +import type { NameValues } from "@/types/hardwareDataType"; import { atom } from "jotai"; export const cpuUsageHistoryAtom = atom([]); export const memoryUsageHistoryAtom = atom([]); export const graphicUsageHistoryAtom = atom([]); -export const cpuTempAtom = atom([]); -export const gpuTempAtom = atom([]); +export const cpuTempAtom = atom([]); +export const cpuFanSpeedAtom = atom([]); +export const gpuTempAtom = atom([]); +export const gpuFanSpeedAtom = atom([]); diff --git a/src/atom/useHardwareInfoAtom.ts b/src/atom/useHardwareInfoAtom.ts index 6fb0c4b..52d2037 100644 --- a/src/atom/useHardwareInfoAtom.ts +++ b/src/atom/useHardwareInfoAtom.ts @@ -5,30 +5,29 @@ import { useEffect } from "react"; const hardInfoAtom = atom({ isFetched: false, - gpus: [], }); export const useHardwareInfoAtom = () => { const [hardwareInfo, setHardInfo] = useAtom(hardInfoAtom); useEffect(() => { - const init = async () => { - try { - const hardwareInfo = await getHardwareInfo(); - setHardInfo(hardwareInfo); + if (!hardwareInfo.isFetched) { + const init = async () => { + try { + const fetchedHardwareInfo = await getHardwareInfo(); - hardwareInfo.isFetched = true; - } catch (e) { - console.error(e); - hardwareInfo.isFetched = false; - } - }; + setHardInfo({ + ...fetchedHardwareInfo, + isFetched: true, + }); + } catch (e) { + console.error(e); + } + }; - // データがなければ取得して更新 - if (!hardwareInfo.isFetched) { init(); } - }, [setHardInfo, hardwareInfo]); + }, [hardwareInfo.isFetched, setHardInfo]); return { hardwareInfo }; }; diff --git a/src/hooks/useHardwareData.ts b/src/hooks/useHardwareData.ts index 7215ddc..d4a94b1 100644 --- a/src/hooks/useHardwareData.ts +++ b/src/hooks/useHardwareData.ts @@ -1,6 +1,8 @@ import { + cpuFanSpeedAtom, cpuTempAtom, cpuUsageHistoryAtom, + gpuFanSpeedAtom, gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, @@ -8,11 +10,12 @@ import { import { chartConfig } from "@/consts/chart"; import { getCpuUsage, + getGpuFanSpeed, getGpuTemperature, getGpuUsage, getMemoryUsage, } from "@/services/hardwareService"; -import type { ChartDataType, Temperatures } from "@/types/hardwareDataType"; +import type { ChartDataType, NameValues } from "@/types/hardwareDataType"; import { type PrimitiveAtom, useSetAtom } from "jotai"; import { useEffect } from "react"; @@ -63,34 +66,53 @@ export const useUsageUpdater = (dataType: ChartDataType) => { }, [setHistory, getUsage]); }; -export const useHardwareTempUpdater = ( - dataType: Exclude, +export const useHardwareUpdater = ( + hardType: Exclude, + dataType: "temp" | "fan", ) => { type AtomActionMapping = { - atom: PrimitiveAtom; - action: () => Promise; + atom: PrimitiveAtom; + action: () => Promise; }; - const mapping: Record, AtomActionMapping> = { + const mapping: Record< + Exclude, + Record<"temp" | "fan", AtomActionMapping> + > = { cpu: { - atom: cpuTempAtom, - action: () => { - console.error("Not implemented"); - return Promise.resolve([]); + temp: { + atom: cpuTempAtom, + action: () => { + console.error("Not implemented"); + return Promise.resolve([]); + }, + }, + fan: { + atom: cpuFanSpeedAtom, + action: () => { + console.error("Not implemented"); + return Promise.resolve([]); + }, }, }, gpu: { - atom: gpuTempAtom, - action: getGpuTemperature, + fan: { + atom: gpuFanSpeedAtom, + action: getGpuFanSpeed, + }, + temp: { + atom: gpuTempAtom, + action: getGpuTemperature, + }, }, }; - const setData = useSetAtom(mapping[dataType].atom); - const getTemp = mapping[dataType].action; + const setData = useSetAtom(mapping[hardType][dataType].atom); + const getData = mapping[hardType][dataType].action; useEffect(() => { const fetchData = async () => { - const temp = await getTemp(); + const temp = await getData(); setData(temp); }; @@ -101,5 +123,5 @@ export const useHardwareTempUpdater = ( }, 10000); return () => clearInterval(intervalId); - }, [setData, getTemp]); + }, [setData, getData]); }; diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index e51e92c..911f16e 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -1,11 +1,13 @@ -import type { HardwareInfo, Temperatures } from "@/types/hardwareDataType"; +import type { HardwareInfo, NameValues } from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; export const getCpuUsage = async (): Promise => { return await invoke("get_cpu_usage"); }; -export const getHardwareInfo = async (): Promise => { +export const getHardwareInfo = async (): Promise< + Exclude +> => { return await invoke("get_hardware_info"); }; @@ -29,6 +31,10 @@ export const getGpuUsageHistory = (seconds: number): Promise => { return invoke("get_gpu_usage_history", { seconds: seconds }); }; -export const getGpuTemperature = async (): Promise => { +export const getGpuTemperature = async (): Promise => { return await invoke("get_gpu_temperature"); }; + +export const getGpuFanSpeed = async (): Promise => { + return await invoke("get_nvidia_gpu_cooler"); +}; diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index 358fd45..379d2f7 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -1,11 +1,13 @@ import { cpuUsageHistoryAtom, + gpuFanSpeedAtom, gpuTempAtom, graphicUsageHistoryAtom, memoryUsageHistoryAtom, } from "@/atom/chart"; import { useHardwareInfoAtom } from "@/atom/useHardwareInfoAtom"; import DoughnutChart from "@/components/charts/DoughnutChart"; +import type { NameValues } from "@/types/hardwareDataType"; import { useAtom } from "jotai"; const InfoTable = ({ @@ -71,11 +73,19 @@ const CPUInfo = () => { const GPUInfo = () => { const [graphicUsageHistory] = useAtom(graphicUsageHistoryAtom); const [gpuTemp] = useAtom(gpuTempAtom); + const [gpuFan] = useAtom(gpuFanSpeedAtom); const { hardwareInfo } = useHardwareInfoAtom(); - const targetTemperature = gpuTemp.find( - (x) => x.name === hardwareInfo.gpus[0].name, - )?.value; + const getTargetInfo = (data: NameValues) => { + return data.find( + (x) => hardwareInfo.gpus && x.name === hardwareInfo.gpus[0].name, + )?.value; + }; + + const targetTemperature = getTargetInfo(gpuTemp); + const targetFanSpeed = getTargetInfo(gpuFan); + + console.log(gpuFan); return ( hardwareInfo.gpus && ( @@ -95,6 +105,14 @@ const GPUInfo = () => { showTitle={false} /> )} + {targetFanSpeed && ( + + )}
    ; From a5e879aa24abd61de2d84747288b5d78c95e3d05 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 28 Sep 2024 16:45:19 +0900 Subject: [PATCH 42/43] feature: process info --- src-tauri/src/commands/hardware.rs | 128 ++++++++++++++++++---- src-tauri/src/main.rs | 9 +- src-tauri/src/services/graphic_service.rs | 2 - src/atom/ui.ts | 2 +- src/components/charts/ProcessTable.tsx | 127 +++++++++++++++++++++ src/services/hardwareService.ts | 10 +- src/template/Dashboard.tsx | 14 ++- src/template/SideMenu.tsx | 36 +++--- src/types/hardwareDataType.ts | 7 ++ 9 files changed, 286 insertions(+), 49 deletions(-) create mode 100644 src/components/charts/ProcessTable.tsx diff --git a/src-tauri/src/commands/hardware.rs b/src-tauri/src/commands/hardware.rs index a7a07ff..7fc6e08 100644 --- a/src-tauri/src/commands/hardware.rs +++ b/src-tauri/src/commands/hardware.rs @@ -1,12 +1,13 @@ use crate::services::graphic_service; use crate::services::system_info_service; -use crate::{log_debug, log_error, log_internal, log_warn}; -use serde::Serialize; +use crate::{log_debug, log_error, log_info, log_internal, log_warn}; +use serde::{Serialize, Serializer}; +use std::collections::HashMap; use std::collections::VecDeque; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; -use sysinfo::System; +use sysinfo::{Pid, ProcessesToUpdate, System}; use tauri::command; pub struct AppState { @@ -15,6 +16,8 @@ pub struct AppState { pub memory_history: Arc>>, pub gpu_history: Arc>>, pub gpu_usage: Arc>, + pub process_cpu_histories: Arc>>>, + pub process_memory_histories: Arc>>>, } /// @@ -27,6 +30,77 @@ const SYSTEM_INFO_INIT_INTERVAL: u64 = 1; /// const HISTORY_CAPACITY: usize = 60; +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessInfo { + pub pid: i32, + pub name: String, + #[serde(serialize_with = "serialize_usage")] + pub cpu_usage: f32, + #[serde(serialize_with = "serialize_usage")] + pub memory_usage: f32, +} + +fn serialize_usage(x: &f32, s: S) -> Result +where + S: Serializer, +{ + if x.fract() == 0.0 { + s.serialize_str(&format!("{:.0}", x)) // 整数のみ + } else { + s.serialize_str(&format!("{:.1}", x)) // 小数点以下1桁まで + } +} + +/// +/// ## プロセスリストを取得 +/// +#[command] +pub fn get_process_list(state: tauri::State<'_, AppState>) -> Vec { + let mut system = state.system.lock().unwrap(); + let process_cpu_histories = state.process_cpu_histories.lock().unwrap(); + let process_memory_histories = state.process_memory_histories.lock().unwrap(); + + system.refresh_processes(ProcessesToUpdate::All); + + system + .processes() + .values() + .map(|process| { + let pid = process.pid(); + + // 5秒間のCPU使用率の平均を計算 + let cpu_usage = if let Some(history) = process_cpu_histories.get(&pid) { + let len = history.len().min(5); // 最大5秒分のデータ + let sum: f32 = history.iter().rev().take(len).sum(); + let avg = sum / len as f32; + + (avg * 10.0).round() / 10.0 + } else { + 0.0 // 履歴がなければ0を返す + }; + + // 5秒間のメモリ使用率の平均を計算 + let memory_usage = if let Some(history) = process_memory_histories.get(&pid) { + let len = history.len().min(5); // 最大5秒分のデータ + let sum: f32 = history.iter().rev().take(len).sum(); + let avg = sum / len as f32; + + (avg * 10.0).round() / 10.0 + } else { + process.memory() as f32 / 1024.0 // 履歴がなければ現在のメモリ使用量を返す + }; + + ProcessInfo { + pid: pid.as_u32() as i32, // プロセスID + name: process.name().to_string_lossy().into_owned(), // プロセス名を取得 + cpu_usage, // 平均CPU使用率 + memory_usage, // 平均メモリ使用率 + } + }) + .collect() +} + /// /// ## CPU使用率(%)を取得 /// @@ -185,6 +259,8 @@ pub fn initialize_system( memory_history: Arc>>, gpu_usage: Arc>, gpu_history: Arc>>, + process_cpu_histories: Arc>>>, + process_memory_histories: Arc>>>, ) { thread::spawn(move || loop { { @@ -193,8 +269,7 @@ pub fn initialize_system( Err(_) => continue, // エラーハンドリング:ロックが破損している場合はスキップ }; - sys.refresh_cpu_all(); - sys.refresh_memory(); + sys.refresh_all(); let cpu_usage = { let cpus = sys.cpus(); @@ -208,16 +283,6 @@ pub fn initialize_system( (used_memory / total_memory * 100.0).round() as f32 }; - //let gpu_usage_value = match get_gpu_usage() { - // Ok(usage) => usage, - // Err(_) => 0.0, // エラーが発生した場合はデフォルト値として0.0を使用 - //}; - - //{ - // let mut gpu = gpu_usage.lock().unwrap(); - // *gpu = gpu_usage_value; - //} - { let mut cpu_hist = cpu_history.lock().unwrap(); if cpu_hist.len() >= HISTORY_CAPACITY { @@ -234,13 +299,32 @@ pub fn initialize_system( memory_hist.push_back(memory_usage); } - //{ - // let mut gpu_hist = gpu_history.lock().unwrap(); - // if gpu_hist.len() >= HISTORY_CAPACITY { - // gpu_hist.pop_front(); - // } - // gpu_hist.push_back(gpu_usage_value); - //} + // 各プロセスごとのCPUおよびメモリ使用率を保存 + { + let mut process_cpu_histories = process_cpu_histories.lock().unwrap(); + let mut process_memory_histories = process_memory_histories.lock().unwrap(); + + for (pid, process) in sys.processes() { + // CPU使用率の履歴を更新 + let cpu_usage = process.cpu_usage() as f32; + let cpu_history = process_cpu_histories.entry(*pid).or_insert(VecDeque::new()); + + if cpu_history.len() >= HISTORY_CAPACITY { + cpu_history.pop_front(); + } + cpu_history.push_back(cpu_usage); + + // メモリ使用率の履歴を更新 + let memory_usage = process.memory() as f32 / 1024.0; // KB単位からMB単位に変換 + let memory_history = + process_memory_histories.entry(*pid).or_insert(VecDeque::new()); + + if memory_history.len() >= HISTORY_CAPACITY { + memory_history.pop_front(); + } + memory_history.push_back(memory_usage); + } + } } thread::sleep(Duration::from_secs(SYSTEM_INFO_INIT_INTERVAL)); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index bc93e53..c375f2c 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -11,7 +11,7 @@ use commands::config; use commands::hardware; use services::window_menu_service; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; use std::sync::{Arc, Mutex}; use sysinfo::System; @@ -25,6 +25,8 @@ fn main() { let memory_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); let gpu_usage = Arc::new(Mutex::new(0.0)); let gpu_history = Arc::new(Mutex::new(VecDeque::with_capacity(60))); + let process_cpu_histories = Arc::new(Mutex::new(HashMap::new())); + let process_memory_histories = Arc::new(Mutex::new(HashMap::new())); let state = hardware::AppState { system: Arc::clone(&system), @@ -32,6 +34,8 @@ fn main() { memory_history: Arc::clone(&memory_history), gpu_usage: Arc::clone(&gpu_usage), gpu_history: Arc::clone(&gpu_history), + process_cpu_histories: Arc::clone(&process_cpu_histories), + process_memory_histories: Arc::clone(&process_memory_histories), }; let menu = window_menu_service::create_setting(); @@ -42,6 +46,8 @@ fn main() { memory_history, gpu_usage, gpu_history, + process_cpu_histories, + process_memory_histories, ); tauri::Builder::default() @@ -50,6 +56,7 @@ fn main() { .manage(app_state) .menu(menu) .invoke_handler(tauri::generate_handler![ + hardware::get_process_list, hardware::get_cpu_usage, hardware::get_hardware_info, hardware::get_memory_usage, diff --git a/src-tauri/src/services/graphic_service.rs b/src-tauri/src/services/graphic_service.rs index 79be04d..4a18b1d 100644 --- a/src-tauri/src/services/graphic_service.rs +++ b/src-tauri/src/services/graphic_service.rs @@ -164,8 +164,6 @@ pub async fn get_nvidia_gpu_cooler_stat() -> Result, nvapi::Statu nvapi::Status::Error })?; - print!("{:?}", cooler_settings); - cooler_infos.push(NameValue { name: gpu.full_name().unwrap_or("Unknown".to_string()), value: cooler_settings[0].current_level.0 as f64, diff --git a/src/atom/ui.ts b/src/atom/ui.ts index ff81c0b..fa195d8 100644 --- a/src/atom/ui.ts +++ b/src/atom/ui.ts @@ -5,4 +5,4 @@ export const modalAtoms = { showSettingsModal: atom(false), }; -export const selectedMenuAtom = atom("usage"); // [TODO] change to dashboard +export const selectedMenuAtom = atom("dashboard"); diff --git a/src/components/charts/ProcessTable.tsx b/src/components/charts/ProcessTable.tsx new file mode 100644 index 0000000..5631359 --- /dev/null +++ b/src/components/charts/ProcessTable.tsx @@ -0,0 +1,127 @@ +import { getProcesses } from "@/services/hardwareService"; +import type { ProcessInfo } from "@/types/hardwareDataType"; +import { CaretDown } from "@phosphor-icons/react"; +import { atom, useAtom, useSetAtom } from "jotai"; +import { useEffect, useState } from "react"; + +const processesAtom = atom([]); + +const ProcessesTable = ({ + defaultItemLength, +}: { defaultItemLength: number }) => { + const [processes] = useAtom(processesAtom); + const setAtom = useSetAtom(processesAtom); + const [showAllItem, setShowAllItem] = useState(false); + const [sortConfig, setSortConfig] = useState<{ + key: keyof ProcessInfo; + direction: "ascending" | "descending"; + } | null>(null); + + useEffect(() => { + const fetchProcesses = async () => { + try { + const processesData = await getProcesses(); + console.log(processesData); + setAtom(processesData); + } catch (error) { + console.error("Failed to fetch processes:", error); + } + }; + + fetchProcesses(); + + const interval = setInterval(fetchProcesses, 3000); + + return () => clearInterval(interval); + }, [setAtom]); + + const sortedProcesses = [...processes]; + if (sortConfig !== null) { + sortedProcesses.sort((a, b) => { + if (a[sortConfig.key] < b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? -1 : 1; + } + if (a[sortConfig.key] > b[sortConfig.key]) { + return sortConfig.direction === "ascending" ? 1 : -1; + } + return 0; + }); + } + + const requestSort = (key: keyof ProcessInfo) => { + let direction: "ascending" | "descending" = "ascending"; + if ( + sortConfig && + sortConfig.key === key && + sortConfig.direction === "ascending" + ) { + direction = "descending"; + } + setSortConfig({ key, direction }); + }; + + return ( +
    +

    Process

    +
    + + + + + + + + + + + {sortedProcesses + .slice(0, showAllItem ? processes.length : defaultItemLength) + .map((process) => ( + + + + + + + ))} + +
    requestSort("pid")} + onKeyDown={() => requestSort("pid")} + > + PID + requestSort("name")} + onKeyDown={() => requestSort("name")} + > + Name + requestSort("cpuUsage")} + onKeyDown={() => requestSort("cpuUsage")} + > + CPU Usage + requestSort("memoryUsage")} + onKeyDown={() => requestSort("memoryUsage")} + > + Memory Usage +
    {process.pid}{process.name}{process.cpuUsage}%{process.memoryUsage} MB
    + {!showAllItem && ( + + )} +
    +
    + ); +}; + +export default ProcessesTable; diff --git a/src/services/hardwareService.ts b/src/services/hardwareService.ts index 911f16e..d3b2916 100644 --- a/src/services/hardwareService.ts +++ b/src/services/hardwareService.ts @@ -1,6 +1,14 @@ -import type { HardwareInfo, NameValues } from "@/types/hardwareDataType"; +import type { + HardwareInfo, + NameValues, + ProcessInfo, +} from "@/types/hardwareDataType"; import { invoke } from "@tauri-apps/api/tauri"; +export const getProcesses = async (): Promise => { + return await invoke("get_process_list"); +}; + export const getCpuUsage = async (): Promise => { return await invoke("get_cpu_usage"); }; diff --git a/src/template/Dashboard.tsx b/src/template/Dashboard.tsx index 379d2f7..fa24444 100644 --- a/src/template/Dashboard.tsx +++ b/src/template/Dashboard.tsx @@ -7,6 +7,7 @@ import { } from "@/atom/chart"; import { useHardwareInfoAtom } from "@/atom/useHardwareInfoAtom"; import DoughnutChart from "@/components/charts/DoughnutChart"; +import ProcessesTable from "@/components/charts/ProcessTable"; import type { NameValues } from "@/types/hardwareDataType"; import { useAtom } from "jotai"; @@ -85,8 +86,6 @@ const GPUInfo = () => { const targetTemperature = getTargetInfo(gpuTemp); const targetFanSpeed = getTargetInfo(gpuFan); - console.log(gpuFan); - return ( hardwareInfo.gpus && ( <> @@ -166,9 +165,14 @@ const Dashboard = () => {
    - - - +
    + + + + + + +
    ); }; diff --git a/src/template/SideMenu.tsx b/src/template/SideMenu.tsx index 9987ab8..d6b8b51 100644 --- a/src/template/SideMenu.tsx +++ b/src/template/SideMenu.tsx @@ -74,23 +74,25 @@ const SideMenu = () => { }); return ( -
    - -
    -
      -
    • -

      Hardware Monitor

      -
    • - - - -
    +
    +
    + +
    +
      +
    • +

      Hardware Monitor

      +
    • + + + +
    +
    ); diff --git a/src/types/hardwareDataType.ts b/src/types/hardwareDataType.ts index ebb6d15..1f2ced7 100644 --- a/src/types/hardwareDataType.ts +++ b/src/types/hardwareDataType.ts @@ -2,6 +2,13 @@ export type ChartDataType = "cpu" | "memory" | "gpu"; export type HardwareDataType = "temp" | "usage" | "clock"; +export type ProcessInfo = { + pid: number; + name: string; + cpuUsage: number; + memoryUsage: number; +}; + export type CpuInfo = { name: string; clock: number; From cf47a28f4d9e533678c51546df921176eb608ab8 Mon Sep 17 00:00:00 2001 From: shm Date: Sat, 28 Sep 2024 21:33:33 +0900 Subject: [PATCH 43/43] Update README.md --- README.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 465e31e..393d59a 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,34 @@ -# create App +# hardware-monitor -## Require +## Dashboard + +![image](https://github.com/user-attachments/assets/9a2bf54f-d6e5-4c20-b0e4-f249fd5b8433) + +## Usage Graph + +![image](https://github.com/user-attachments/assets/b8fa7d67-a015-487f-aeb4-f43306d28f54) + + +## Development + +### Require - Node.js 20 - Rust -## Creating a project +### Creating a project ```bash npm ci ``` -## Developing +### Developing ```bash npm run tauri dev ``` -## Building +### Building ```bash npm run tauri build