chore:: apply prettier formatting to entire codebase

This commit is contained in:
Draculabo committed 2026-01-19 19:40:11 +08:00
1 parent 695d787b01
commit a343e0c1ae
43 files changed
+349 -426

No files matched your search

-1
View File
@@ -8,5 +8,4 @@
"tabWidth": 2, "tabWidth": 2,
"proseWrap": "preserve", "proseWrap": "preserve",
"tailwindFunctions": ["clsx", "tw"] "tailwindFunctions": ["clsx", "tw"]
} }
+8 -1
View File
@@ -8,39 +8,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.2.0] - 2026-01-16 ## [0.2.0] - 2026-01-16
### New Features ### New Features
- Enhanced cloudHandler to inject minimal auth state when database entry is missing, improving onboarding reliability. - Enhanced cloudHandler to inject minimal auth state when database entry is missing, improving onboarding reliability.
- Implemented stability fixes and enhanced error handling across the application. - Implemented stability fixes and enhanced error handling across the application.
### Improvements ### Improvements
- Upgraded Electron from 32.3.3 to 37.3.1 for improved performance and security. - Upgraded Electron from 32.3.3 to 37.3.1 for improved performance and security.
- Conditionally include plugins based on start command in forge.config.ts for better build flexibility. - Conditionally include plugins based on start command in forge.config.ts for better build flexibility.
### Bug Fixes ### Bug Fixes
- Fixed "Converting circular structure to JSON" error. - Fixed "Converting circular structure to JSON" error.
### Documentation ### Documentation
- Added curly brace constraints for conditional statements. - Added curly brace constraints for conditional statements.
- Fixed incorrect reference documentation name. - Fixed incorrect reference documentation name.
## [0.1.1] - 2026-01-11 ## [0.1.1] - 2026-01-11
### Bug Fixes ### Bug Fixes
- Fix Antigravity visibility issue on account switch. (Fixed [#19](https://github.com/Draculabo/AntigravityManager/issues/19)) - Fix Antigravity visibility issue on account switch. (Fixed [#19](https://github.com/Draculabo/AntigravityManager/issues/19))
## [0.1.0] - 2026-01-10 ## [0.1.0] - 2026-01-10
### New Features ### New Features
- LAN Connection Support: Users can now connect via Local Area Network (LAN) for improved flexibility and internal environment support. - LAN Connection Support: Users can now connect via Local Area Network (LAN) for improved flexibility and internal environment support.
- Antigravity Integration: Added native support and adaptation for Antigravity, enhancing overall compatibility. - Antigravity Integration: Added native support and adaptation for Antigravity, enhancing overall compatibility.
- Local API Proxy: Built-in OpenAI/Anthropic compatible proxy server. - Local API Proxy: Built-in OpenAI/Anthropic compatible proxy server.
### Bug Fixes ### Bug Fixes
- Reverse Proxy Issue: Resolved a critical error occurring during reverse proxy configurations. (Fixed [#11](https://github.com/Draculabo/AntigravityManager/issues/11))
- Reverse Proxy Issue: Resolved a critical error occurring during reverse proxy configurations. (Fixed [#11](https://github.com/Draculabo/AntigravityManager/issues/11))
## [0.0.1] - 2025-12-22 ## [0.0.1] - 2025-12-22
### Added ### Added
- Initial release of Antigravity Manager - Initial release of Antigravity Manager
- Multi-account management for Google Gemini and Claude - Multi-account management for Google Gemini and Claude
- Real-time quota monitoring - Real-time quota monitoring
+11 -10
View File
@@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo
Examples of behavior that contributes to creating a positive environment include: Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language - Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences - Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism - Gracefully accepting constructive criticism
* Focusing on what is best for the community - Focusing on what is best for the community
* Showing empathy towards other community members - Showing empathy towards other community members
Examples of unacceptable behavior by participants include: Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances - The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks - Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission - Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting - Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities ## Our Responsibilities
@@ -45,4 +45,5 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage], versi
[homepage]: https://www.contributor-covenant.org [homepage]: https://www.contributor-covenant.org
--- ---
**Note**: Please replace `[INSERT EMAIL ADDRESS]` with your actual contact email if you wish to receive reports. **Note**: Please replace `[INSERT EMAIL ADDRESS]` with your actual contact email if you wish to receive reports.
+5 -1
View File
@@ -53,7 +53,7 @@ npm start
### Available Scripts ### Available Scripts
| Command | Description | | Command | Description |
|---------|-------------| | ---------------------- | ----------------------------------- |
| `npm start` | Start the app in development mode | | `npm start` | Start the app in development mode |
| `npm run lint` | Run ESLint to check for code issues | | `npm run lint` | Run ESLint to check for code issues |
| `npm run format:write` | Format code with Prettier | | `npm run format:write` | Format code with Prettier |
@@ -66,6 +66,7 @@ npm start
## ✏️ Making Changes ## ✏️ Making Changes
1. **Create a new branch** from `main`: 1. **Create a new branch** from `main`:
```bash ```bash
git checkout -b feature/your-feature-name git checkout -b feature/your-feature-name
# or # or
@@ -73,6 +74,7 @@ npm start
``` ```
2. **Make your changes** and commit them: 2. **Make your changes** and commit them:
```bash ```bash
git add . git add .
git commit -m "feat: add amazing new feature" git commit -m "feat: add amazing new feature"
@@ -97,6 +99,7 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/). Each com
``` ```
**Types:** **Types:**
- `feat`: A new feature - `feat`: A new feature
- `fix`: A bug fix - `fix`: A bug fix
- `docs`: Documentation only changes - `docs`: Documentation only changes
@@ -107,6 +110,7 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/). Each com
- `chore`: Changes to build process or auxiliary tools - `chore`: Changes to build process or auxiliary tools
**Examples:** **Examples:**
``` ```
feat(auth): add Google OAuth support feat(auth): add Google OAuth support
fix(quota): resolve quota refresh timeout issue fix(quota): resolve quota refresh timeout issue
+4 -4
View File
@@ -174,7 +174,6 @@
<img src="docs/assets/screenshot-setting.png" alt="设置界面" width="48%" /> <img src="docs/assets/screenshot-setting.png" alt="设置界面" width="48%" />
</p> </p>
--- ---
## 🚀 快速开始 ## 🚀 快速开始
@@ -184,7 +183,7 @@
从 [Releases](https://github.com/Draculabo/AntigravityManager/releases) 页面下载适合你平台的最新版本。 从 [Releases](https://github.com/Draculabo/AntigravityManager/releases) 页面下载适合你平台的最新版本。
| 平台 | 下载链接 | | 平台 | 下载链接 |
|------|----------| | ------- | ------------------------------------------------------------------------------ |
| Windows | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) | | Windows | [.exe 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
| macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) | | macOS | [.dmg 安装包](https://github.com/Draculabo/AntigravityManager/releases/latest) |
| Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) | | Linux | [.deb / .rpm](https://github.com/Draculabo/AntigravityManager/releases/latest) |
@@ -218,7 +217,7 @@ npm run make
## 🛠️ 技术栈 ## 🛠️ 技术栈
| 类别 | 技术 | | 类别 | 技术 |
|------|------| | ------------ | ------------------------------------------------------------------------------------------------------------------- |
| **核心** | [Electron](https://www.electronjs.org/)、[React](https://react.dev/)、[TypeScript](https://www.typescriptlang.org/) | | **核心** | [Electron](https://www.electronjs.org/)、[React](https://react.dev/)、[TypeScript](https://www.typescriptlang.org/) |
| **构建工具** | [Vite](https://vitejs.dev/) | | **构建工具** | [Vite](https://vitejs.dev/) |
| **样式** | [TailwindCSS](https://tailwindcss.com/)、[Shadcn UI](https://ui.shadcn.com/) | | **样式** | [TailwindCSS](https://tailwindcss.com/)、[Shadcn UI](https://ui.shadcn.com/) |
@@ -233,7 +232,7 @@ npm run make
### 可用脚本 ### 可用脚本
| 命令 | 描述 | | 命令 | 描述 |
|------|------| | ---------------------- | ------------------------ |
| `npm start` | 启动开发模式 | | `npm start` | 启动开发模式 |
| `npm run lint` | 运行 ESLint 检查 | | `npm run lint` | 运行 ESLint 检查 |
| `npm run format:write` | 使用 Prettier 格式化代码 | | `npm run format:write` | 使用 Prettier 格式化代码 |
@@ -265,6 +264,7 @@ AntigravityManager/
<summary><b>Q: 程序无法启动怎么办?</b></summary> <summary><b>Q: 程序无法启动怎么办?</b></summary>
请检查: 请检查:
1. 确保已安装所有依赖:`npm install` 1. 确保已安装所有依赖:`npm install`
2. 检查 Node.js 版本是否 >= 18 2. 检查 Node.js 版本是否 >= 18
3. 尝试删除 `node_modules` 后重新安装 3. 尝试删除 `node_modules` 后重新安装
+1 -1
View File
@@ -27,7 +27,7 @@ Master keys are stored in the OS native credential manager via `keytar`, never i
## 📋 Supported Versions ## 📋 Supported Versions
| Version | Supported | | Version | Supported |
| ------- | ------------------ | | -------- | ------------------ |
| Latest | :white_check_mark: | | Latest | :white_check_mark: |
| < Latest | :x: | | < Latest | :x: |
+10
View File
@@ -5,17 +5,20 @@
## 1. 核心功能 ## 1. 核心功能
### 1.1 账号池管理 ### 1.1 账号池管理
- **添加账号**:支持通过 Google OAuth 授权码添加账号。 - **添加账号**:支持通过 Google OAuth 授权码添加账号。
- **列表展示**:展示所有已添加的账号,包括头像、邮箱、最后使用时间。 - **列表展示**:展示所有已添加的账号,包括头像、邮箱、最后使用时间。
- **状态监控**:实时显示账号状态(Active, Rate Limited, Expired)以及是否为当前活动账号。 - **状态监控**:实时显示账号状态(Active, Rate Limited, Expired)以及是否为当前活动账号。
- **删除账号**:支持从本地数据库中移除账号。 - **删除账号**:支持从本地数据库中移除账号。
### 1.2 配额实时监控 ### 1.2 配额实时监控
- **多模型支持**:分别监控 `gemini-pro`, `claude-3-5-sonnet` 等模型的配额使用情况。 - **多模型支持**:分别监控 `gemini-pro`, `claude-3-5-sonnet` 等模型的配额使用情况。
- **可视化展示**:通过进度条和颜色(绿/黄/红)直观展示剩余配额百分比。 - **可视化展示**:通过进度条和颜色(绿/黄/红)直观展示剩余配额百分比。
- **自动/手动刷新**:支持手动刷新配额,系统也会在切换前自动检查。 - **自动/手动刷新**:支持手动刷新配额,系统也会在切换前自动检查。
### 1.4 智能自动切换 (Intelligent Auto-Switching) ### 1.4 智能自动切换 (Intelligent Auto-Switching)
- **无限池模式**:当检测到当前账号配额不足(<5%)或被限流时,系统会自动寻找最佳备用账号并切换。 - **无限池模式**:当检测到当前账号配额不足(<5%)或被限流时,系统会自动寻找最佳备用账号并切换。
- **后台监控**:内置 `CloudMonitorService`, 默认每 5 分钟轮询一次所有账号的 Quota 状态。 - **后台监控**:内置 `CloudMonitorService`, 默认每 5 分钟轮询一次所有账号的 Quota 状态。
- **全局开关**:用户可在界面上一键开启或关闭此功能。 - **全局开关**:用户可在界面上一键开启或关闭此功能。
@@ -23,6 +26,7 @@
## 2. 技术实现 ## 2. 技术实现
### 2.1 数据库设计 (`cloud_accounts.db`) ### 2.1 数据库设计 (`cloud_accounts.db`)
使用 SQLite 存储账号信息,独立于 IDE 的本地数据库。 使用 SQLite 存储账号信息,独立于 IDE 的本地数据库。
```sql ```sql
@@ -48,6 +52,7 @@ CREATE TABLE settings (
``` ```
### 2.2 IPC 接口设计 ### 2.2 IPC 接口设计
后端通过 `src/ipc/cloud` 提供以下 oRPC 接口: 后端通过 `src/ipc/cloud` 提供以下 oRPC 接口:
- `addGoogleAccount(authCode: string)`: 交换 Token 并保存账号。 - `addGoogleAccount(authCode: string)`: 交换 Token 并保存账号。
@@ -59,15 +64,18 @@ CREATE TABLE settings (
- `forcePollCloudMonitor()`: 手动触发后台轮询。 - `forcePollCloudMonitor()`: 手动触发后台轮询。
### 2.3 Token 注入原理 (`ProtobufUtils`) ### 2.3 Token 注入原理 (`ProtobufUtils`)
IDE 将鉴权信息存储在 `state.vscdb` 的 `ItemTable` 表中,key 为 `jetskiStateSync.agentManagerInitState`。 IDE 将鉴权信息存储在 `state.vscdb` 的 `ItemTable` 表中,key 为 `jetskiStateSync.agentManagerInitState`。
value 是一个 Base64 编码的 Protobuf 二进制数据。 value 是一个 Base64 编码的 Protobuf 二进制数据。
我们实现了 `src/utils/protobuf.ts` 工具类,能够: 我们实现了 `src/utils/protobuf.ts` 工具类,能够:
1. **解码**:解析 Varint 和 Length-Delimited 字段。 1. **解码**:解析 Varint 和 Length-Delimited 字段。
2. **修改**:定位并移除旧的 Field 6 (OAuthTokenInfo)。 2. **修改**:定位并移除旧的 Field 6 (OAuthTokenInfo)。
3. **重构**:根据新的 Access Token 和 Refresh Token 构建新的 Field 6 并插入。 3. **重构**:根据新的 Access Token 和 Refresh Token 构建新的 Field 6 并插入。
### 2.4 切换流程 ### 2.4 切换流程
1. **Token 检查**:检查目标账号 Token 是否临期,如需刷新则自动刷新。 1. **Token 检查**:检查目标账号 Token 是否临期,如需刷新则自动刷新。
2. **停止进程**:优雅关闭 Antigravity 进程。 2. **停止进程**:优雅关闭 Antigravity 进程。
3. **注入 Token**:调用 `CloudAccountRepo.injectCloudToken` 修改 IDE 数据库。 3. **注入 Token**:调用 `CloudAccountRepo.injectCloudToken` 修改 IDE 数据库。
@@ -75,12 +83,14 @@ value 是一个 Base64 编码的 Protobuf 二进制数据。
5. **重启进程**:重新启动 IDE,使其加载新的凭证。 5. **重启进程**:重新启动 IDE,使其加载新的凭证。
### 2.5 自动切换逻辑 (`AutoSwitchService`) ### 2.5 自动切换逻辑 (`AutoSwitchService`)
1. **监控**:`CloudMonitorService` 定时(5分钟)轮询所有账号配额。 1. **监控**:`CloudMonitorService` 定时(5分钟)轮询所有账号配额。
2. **判断**:如果当前活动账号的所有关键模型配额低于 5%,或状态为 `rate_limited`。 2. **判断**:如果当前活动账号的所有关键模型配额低于 5%,或状态为 `rate_limited`。
3. **择优**:从账号池中筛选状态为 `active` 且配额充足的账号,按配额剩余量排序。 3. **择优**:从账号池中筛选状态为 `active` 且配额充足的账号,按配额剩余量排序。
4. **执行**:自动调用 `switchCloudAccount` 完成切换,并通知用户。 4. **执行**:自动调用 `switchCloudAccount` 完成切换,并通知用户。
### 2.6 安全加固 (Security Hardening) ### 2.6 安全加固 (Security Hardening)
- **密钥管理**:使用操作系统原生凭据管理器(Windows Credential Manager / macOS Keychain)通过 `keytar` 库安全存储 Master Key(AES-256)。 - **密钥管理**:使用操作系统原生凭据管理器(Windows Credential Manager / macOS Keychain)通过 `keytar` 库安全存储 Master Key(AES-256)。
- **数据加密**:所有敏感数据(`token_json`, `quota_json`)在存入 SQLite 数据库前均使用 `AES-256-GCM` 算法加密。 - **数据加密**:所有敏感数据(`token_json`, `quota_json`)在存入 SQLite 数据库前均使用 `AES-256-GCM` 算法加密。
- **自动迁移**:应用启动时会自动检测并迁移旧的明文数据,确保安全性平滑升级。 - **自动迁移**:应用启动时会自动检测并迁移旧的明文数据,确保安全性平滑升级。
+6 -6
View File
@@ -1,23 +1,23 @@
import { defineConfig, devices } from "@playwright/test"; import { defineConfig, devices } from '@playwright/test';
/** /**
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
testDir: "./src/tests/e2e", testDir: './src/tests/e2e',
fullyParallel: false, fullyParallel: false,
forbidOnly: !!process.env.CI, forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0, retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
reporter: "html", reporter: 'html',
use: { use: {
trace: "on-first-retry", trace: 'on-first-retry',
}, },
projects: [ projects: [
{ {
name: "chromium", name: 'chromium',
use: { ...devices["Desktop Chrome"] }, use: { ...devices['Desktop Chrome'] },
}, },
], ],
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
import { ipc } from "@/ipc/manager"; import { ipc } from '@/ipc/manager';
export function listAccounts() { export function listAccounts() {
return ipc.client.account.listAccounts(); return ipc.client.account.listAccounts();
+1 -1
View File
@@ -1,4 +1,4 @@
import { ipc } from "@/ipc/manager"; import { ipc } from '@/ipc/manager';
export function getPlatform() { export function getPlatform() {
return ipc.client.app.currentPlatfom(); return ipc.client.app.currentPlatfom();
+2 -2
View File
@@ -1,5 +1,5 @@
import { ipc } from "@/ipc/manager"; import { ipc } from '@/ipc/manager';
import { AccountBackupData, Account } from "@/types/account"; import { AccountBackupData, Account } from '@/types/account';
export function backupAccount(account: Account) { export function backupAccount(account: Account) {
return ipc.client.database.backupAccount(account); return ipc.client.database.backupAccount(account);
+9 -11
View File
@@ -1,6 +1,6 @@
import { ThemeMode } from "@/types/theme-mode"; import { ThemeMode } from '@/types/theme-mode';
import { ipc } from "@/ipc/manager"; import { ipc } from '@/ipc/manager';
import { LOCAL_STORAGE_KEYS } from "@/constants"; import { LOCAL_STORAGE_KEYS } from '@/constants';
export interface ThemePreferences { export interface ThemePreferences {
system: ThemeMode; system: ThemeMode;
@@ -9,9 +9,7 @@ export interface ThemePreferences {
export async function getCurrentTheme(): Promise<ThemePreferences> { export async function getCurrentTheme(): Promise<ThemePreferences> {
const currentTheme = await ipc.client.theme.getCurrentThemeMode(); const currentTheme = await ipc.client.theme.getCurrentThemeMode();
const localTheme = localStorage.getItem( const localTheme = localStorage.getItem(LOCAL_STORAGE_KEYS.THEME) as ThemeMode | null;
LOCAL_STORAGE_KEYS.THEME,
) as ThemeMode | null;
return { return {
system: currentTheme, system: currentTheme,
@@ -20,7 +18,7 @@ export async function getCurrentTheme(): Promise<ThemePreferences> {
} }
export async function setTheme(newTheme: ThemeMode) { export async function setTheme(newTheme: ThemeMode) {
const isDarkMode = newTheme === "dark"; const isDarkMode = newTheme === 'dark';
await ipc.client.theme.setThemeMode(newTheme); await ipc.client.theme.setThemeMode(newTheme);
localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme); localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme);
updateDocumentTheme(isDarkMode); updateDocumentTheme(isDarkMode);
@@ -28,7 +26,7 @@ export async function setTheme(newTheme: ThemeMode) {
export async function toggleTheme() { export async function toggleTheme() {
const isDarkMode = await ipc.client.theme.toggleThemeMode(); const isDarkMode = await ipc.client.theme.toggleThemeMode();
const newTheme = isDarkMode ? "dark" : "light"; const newTheme = isDarkMode ? 'dark' : 'light';
updateDocumentTheme(isDarkMode); updateDocumentTheme(isDarkMode);
localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme); localStorage.setItem(LOCAL_STORAGE_KEYS.THEME, newTheme);
@@ -37,7 +35,7 @@ export async function toggleTheme() {
export async function syncWithLocalTheme() { export async function syncWithLocalTheme() {
const { local } = await getCurrentTheme(); const { local } = await getCurrentTheme();
if (!local) { if (!local) {
setTheme("system"); setTheme('system');
return; return;
} }
@@ -46,8 +44,8 @@ export async function syncWithLocalTheme() {
function updateDocumentTheme(isDarkMode: boolean) { function updateDocumentTheme(isDarkMode: boolean) {
if (isDarkMode) { if (isDarkMode) {
document.documentElement.classList.add("dark"); document.documentElement.classList.add('dark');
} else { } else {
document.documentElement.classList.remove("dark"); document.documentElement.classList.remove('dark');
} }
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { ipc } from "../ipc/manager"; import { ipc } from '../ipc/manager';
export async function minimizeWindow() { export async function minimizeWindow() {
await ipc.client.window.minimizeWindow(); await ipc.client.window.minimizeWindow();
+17 -25
View File
@@ -1,21 +1,17 @@
import React from "react"; import React from 'react';
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { import { isProcessRunning, startAntigravity, closeAntigravity } from '@/actions/process';
isProcessRunning, import { Button } from '@/components/ui/button';
startAntigravity, import { cn } from '@/lib/utils';
closeAntigravity, import { Play, Square, Loader2 } from 'lucide-react';
} from "@/actions/process"; import { useTranslation } from 'react-i18next';
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Play, Square, Loader2 } from "lucide-react";
import { useTranslation } from "react-i18next";
export const StatusBar: React.FC = () => { export const StatusBar: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: isRunning, isLoading } = useQuery({ const { data: isRunning, isLoading } = useQuery({
queryKey: ["process", "status"], queryKey: ['process', 'status'],
queryFn: isProcessRunning, queryFn: isProcessRunning,
refetchInterval: 2000, refetchInterval: 2000,
}); });
@@ -23,14 +19,14 @@ export const StatusBar: React.FC = () => {
const startMutation = useMutation({ const startMutation = useMutation({
mutationFn: startAntigravity, mutationFn: startAntigravity,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["process", "status"] }); queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
}, },
}); });
const stopMutation = useMutation({ const stopMutation = useMutation({
mutationFn: closeAntigravity, mutationFn: closeAntigravity,
onSuccess: () => { onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["process", "status"] }); queryClient.invalidateQueries({ queryKey: ['process', 'status'] });
}, },
}); });
@@ -47,10 +43,10 @@ export const StatusBar: React.FC = () => {
return ( return (
<div <div
className={cn( className={cn(
"flex items-center justify-between px-4 py-2 text-sm font-medium transition-colors", 'flex items-center justify-between px-4 py-2 text-sm font-medium transition-colors',
isRunning isRunning
? "bg-green-100 text-green-900 dark:bg-green-900/30 dark:text-green-100" ? 'bg-green-100 text-green-900 dark:bg-green-900/30 dark:text-green-100'
: "bg-red-100 text-red-900 dark:bg-red-900/30 dark:text-red-100", : 'bg-red-100 text-red-900 dark:bg-red-900/30 dark:text-red-100',
)} )}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -62,11 +58,7 @@ export const StatusBar: React.FC = () => {
<div className="h-3 w-3 rounded-full bg-red-500" /> <div className="h-3 w-3 rounded-full bg-red-500" />
)} )}
<span> <span>
{isLoading {isLoading ? t('status.checking') : isRunning ? t('status.running') : t('status.stopped')}
? t("status.checking")
: isRunning
? t("status.running")
: t("status.stopped")}
</span> </span>
</div> </div>
<Button <Button
@@ -75,8 +67,8 @@ export const StatusBar: React.FC = () => {
onClick={handleToggle} onClick={handleToggle}
disabled={isLoading || isPending} disabled={isLoading || isPending}
className={cn( className={cn(
"h-8 px-3 hover:bg-white/20", 'h-8 px-3 hover:bg-white/20',
isRunning ? "hover:text-green-900" : "hover:text-red-900", isRunning ? 'hover:text-green-900' : 'hover:text-red-900',
)} )}
> >
{isPending ? ( {isPending ? (
@@ -86,7 +78,7 @@ export const StatusBar: React.FC = () => {
) : ( ) : (
<Play className="mr-2 h-4 w-4 fill-current" /> <Play className="mr-2 h-4 w-4 fill-current" />
)} )}
{isRunning ? t("action.stop") : t("action.start")} {isRunning ? t('action.stop') : t('action.start')}
</Button> </Button>
</div> </div>
); );
+4 -4
View File
@@ -1,12 +1,12 @@
import { Link } from "@tanstack/react-router"; import { Link } from '@tanstack/react-router';
import { import {
NavigationMenu as NavigationMenuBase, NavigationMenu as NavigationMenuBase,
NavigationMenuItem, NavigationMenuItem,
NavigationMenuLink, NavigationMenuLink,
NavigationMenuList, NavigationMenuList,
navigationMenuTriggerStyle, navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"; } from '@/components/ui/navigation-menu';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
export default function NavigationMenu() { export default function NavigationMenu() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -16,7 +16,7 @@ export default function NavigationMenu() {
<NavigationMenuList> <NavigationMenuList>
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuLink asChild className={navigationMenuTriggerStyle()}> <NavigationMenuLink asChild className={navigationMenuTriggerStyle()}>
<Link to="/">{t("titleHomePage")}</Link> <Link to="/">{t('titleHomePage')}</Link>
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuItem> </NavigationMenuItem>
</NavigationMenuList> </NavigationMenuList>
+3 -3
View File
@@ -1,6 +1,6 @@
import { Moon } from "lucide-react"; import { Moon } from 'lucide-react';
import { Button } from "@/components/ui/button"; import { Button } from '@/components/ui/button';
import { toggleTheme } from "@/actions/theme"; import { toggleTheme } from '@/actions/theme';
export default function ToggleTheme() { export default function ToggleTheme() {
return ( return (
+6 -9
View File
@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from 'react';
import * as AvatarPrimitive from "@radix-ui/react-avatar"; import * as AvatarPrimitive from '@radix-ui/react-avatar';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const Avatar = React.forwardRef< const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>, React.ElementRef<typeof AvatarPrimitive.Root>,
@@ -8,10 +8,7 @@ const Avatar = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Root <AvatarPrimitive.Root
ref={ref} ref={ref}
className={cn( className={cn('relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', className)}
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className,
)}
{...props} {...props}
/> />
)); ));
@@ -23,7 +20,7 @@ const AvatarImage = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<AvatarPrimitive.Image <AvatarPrimitive.Image
ref={ref} ref={ref}
className={cn("aspect-square h-full w-full", className)} className={cn('aspect-square h-full w-full', className)}
{...props} {...props}
/> />
)); ));
@@ -36,7 +33,7 @@ const AvatarFallback = React.forwardRef<
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
ref={ref} ref={ref}
className={cn( className={cn(
"bg-muted flex h-full w-full items-center justify-center rounded-full", 'bg-muted flex h-full w-full items-center justify-center rounded-full',
className, className,
)} )}
{...props} {...props}
+11 -15
View File
@@ -1,35 +1,31 @@
import * as React from "react"; import * as React from 'react';
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{ {
variants: { variants: {
variant: { variant: {
default: default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: "text-foreground", outline: 'text-foreground',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
}, },
}, },
); );
export interface BadgeProps export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>, extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
} }
export { Badge, badgeVariants }; export { Badge, badgeVariants };
+35 -62
View File
@@ -1,82 +1,55 @@
import * as React from "react"; import * as React from 'react';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const Card = React.forwardRef< const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} ref={ref}
className={cn( className={cn('bg-card text-card-foreground rounded-xl border shadow', className)}
"bg-card text-card-foreground rounded-xl border shadow",
className,
)}
{...props} {...props}
/> />
)); ),
Card.displayName = "Card"; );
Card.displayName = 'Card';
const CardHeader = React.forwardRef< const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement> <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
>(({ className, ...props }, ref) => ( ),
<div );
ref={ref} CardHeader.displayName = 'CardHeader';
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef< const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
HTMLParagraphElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3 <h3
ref={ref} ref={ref}
className={cn("leading-none font-semibold tracking-tight", className)} className={cn('leading-none font-semibold tracking-tight', className)}
{...props} {...props}
/> />
)); ),
CardTitle.displayName = "CardTitle"; );
CardTitle.displayName = 'CardTitle';
const CardDescription = React.forwardRef< const CardDescription = React.forwardRef<
HTMLParagraphElement, HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement> React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<p <p ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />
ref={ref}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)); ));
CardDescription.displayName = "CardDescription"; CardDescription.displayName = 'CardDescription';
const CardContent = React.forwardRef< const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement> <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
>(({ className, ...props }, ref) => ( ),
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> );
)); CardContent.displayName = 'CardContent';
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef< const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
HTMLDivElement, ({ className, ...props }, ref) => (
React.HTMLAttributes<HTMLDivElement> <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
>(({ className, ...props }, ref) => ( ),
<div );
ref={ref} CardFooter.displayName = 'CardFooter';
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export { export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};
+21 -34
View File
@@ -1,7 +1,7 @@
import * as React from "react"; import * as React from 'react';
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { Check, ChevronRight, Circle } from "lucide-react"; import { Check, ChevronRight, Circle } from 'lucide-react';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
@@ -19,8 +19,8 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none", 'focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none',
inset && "pl-8", inset && 'pl-8',
className, className,
)} )}
{...props} {...props}
@@ -29,8 +29,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
)); ));
DropdownMenuSubTrigger.displayName = DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef< const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>, React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@@ -39,14 +38,13 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg", 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
className, className,
)} )}
{...props} {...props}
/> />
)); ));
DropdownMenuSubContent.displayName = DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef< const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>, React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@@ -57,8 +55,8 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md", 'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className, className,
)} )}
{...props} {...props}
@@ -76,8 +74,8 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && "pl-8", inset && 'pl-8',
className, className,
)} )}
{...props} {...props}
@@ -92,7 +90,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className, className,
)} )}
checked={checked} checked={checked}
@@ -106,8 +104,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
)); ));
DropdownMenuCheckboxItem.displayName = DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef< const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>, React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@@ -116,7 +113,7 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className, className,
)} )}
{...props} {...props}
@@ -139,11 +136,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
)}
{...props} {...props}
/> />
)); ));
@@ -155,24 +148,18 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("bg-muted -mx-1 my-1 h-px", className)} className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props} {...props}
/> />
)); ));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({ const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
); );
}; };
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
export { export {
DropdownMenu, DropdownMenu,
+7 -12
View File
@@ -1,22 +1,17 @@
import * as React from "react"; import * as React from 'react';
import * as LabelPrimitive from "@radix-ui/react-label"; import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const labelVariants = cva( 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< const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>, React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<LabelPrimitive.Root <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
)); ));
Label.displayName = LabelPrimitive.Root.displayName; Label.displayName = LabelPrimitive.Root.displayName;
+19 -26
View File
@@ -1,10 +1,10 @@
"use client"; 'use client';
import * as React from "react"; import * as React from 'react';
import * as SelectPrimitive from "@radix-ui/react-select"; import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from "lucide-react"; import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const Select = SelectPrimitive.Root; const Select = SelectPrimitive.Root;
@@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger <SelectPrimitive.Trigger
ref={ref} ref={ref}
className={cn( className={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", 'border-input bg-background ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-10 w-full items-center justify-between rounded-md border px-3 py-2 text-sm focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className, className,
)} )}
{...props} {...props}
@@ -38,10 +38,7 @@ const SelectScrollUpButton = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton <SelectPrimitive.ScrollUpButton
ref={ref} ref={ref}
className={cn( className={cn('flex cursor-default items-center justify-center py-1', className)}
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props} {...props}
> >
<ChevronUp className="h-4 w-4" /> <ChevronUp className="h-4 w-4" />
@@ -55,29 +52,25 @@ const SelectScrollDownButton = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton <SelectPrimitive.ScrollDownButton
ref={ref} ref={ref}
className={cn( className={cn('flex cursor-default items-center justify-center py-1', className)}
"flex cursor-default items-center justify-center py-1",
className,
)}
{...props} {...props}
> >
<ChevronDown className="h-4 w-4" /> <ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton> </SelectPrimitive.ScrollDownButton>
)); ));
SelectScrollDownButton.displayName = SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = React.forwardRef< const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>, React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => ( >(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal> <SelectPrimitive.Portal>
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md", 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md',
position === "popper" && position === 'popper' &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className, className,
)} )}
position={position} position={position}
@@ -86,9 +79,9 @@ const SelectContent = React.forwardRef<
<SelectScrollUpButton /> <SelectScrollUpButton />
<SelectPrimitive.Viewport <SelectPrimitive.Viewport
className={cn( className={cn(
"p-1", 'p-1',
position === "popper" && position === 'popper' &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]", 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
)} )}
> >
{children} {children}
@@ -105,7 +98,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Label <SelectPrimitive.Label
ref={ref} ref={ref}
className={cn("py-1.5 pr-2 pl-8 text-sm font-semibold", className)} className={cn('py-1.5 pr-2 pl-8 text-sm font-semibold', className)}
{...props} {...props}
/> />
)); ));
@@ -118,7 +111,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item <SelectPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'focus:bg-accent focus:text-accent-foreground relative flex w-full cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className, className,
)} )}
{...props} {...props}
@@ -140,7 +133,7 @@ const SelectSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SelectPrimitive.Separator <SelectPrimitive.Separator
ref={ref} ref={ref}
className={cn("bg-muted -mx-1 my-1 h-px", className)} className={cn('bg-muted -mx-1 my-1 h-px', className)}
{...props} {...props}
/> />
)); ));
+5 -5
View File
@@ -1,6 +1,6 @@
import * as React from "react"; import * as React from 'react';
import * as SwitchPrimitive from "@radix-ui/react-switch"; import * as SwitchPrimitive from '@radix-ui/react-switch';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const Switch = React.forwardRef< const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitive.Root>, React.ElementRef<typeof SwitchPrimitive.Root>,
@@ -8,7 +8,7 @@ const Switch = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<SwitchPrimitive.Root <SwitchPrimitive.Root
className={cn( className={cn(
"peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50", 'peer focus-visible:ring-ring focus-visible:ring-offset-background data-[state=checked]:bg-primary data-[state=unchecked]:bg-input inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
className, className,
)} )}
{...props} {...props}
@@ -16,7 +16,7 @@ const Switch = React.forwardRef<
> >
<SwitchPrimitive.Thumb <SwitchPrimitive.Thumb
className={cn( className={cn(
"bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0", 'bg-background pointer-events-none block h-4 w-4 rounded-full shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0',
)} )}
/> />
</SwitchPrimitive.Root> </SwitchPrimitive.Root>
+15 -16
View File
@@ -1,8 +1,8 @@
import * as React from "react"; import * as React from 'react';
import * as ToastPrimitives from "@radix-ui/react-toast"; import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from 'class-variance-authority';
import { X } from "lucide-react"; import { X } from 'lucide-react';
import { cn } from "@/lib/utils"; import { cn } from '@/lib/utils';
const ToastProvider = ToastPrimitives.Provider; const ToastProvider = ToastPrimitives.Provider;
@@ -13,7 +13,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]", 'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:top-auto sm:right-0 sm:bottom-0 sm:flex-col md:max-w-[420px]',
className, className,
)} )}
{...props} {...props}
@@ -22,25 +22,24 @@ const ToastViewport = React.forwardRef<
ToastViewport.displayName = ToastPrimitives.Viewport.displayName; ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva( const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", 'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{ {
variants: { variants: {
variant: { variant: {
default: "border bg-background text-foreground", default: 'border bg-background text-foreground',
destructive: destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground", 'destructive group border-destructive bg-destructive text-destructive-foreground',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
}, },
}, },
); );
const Toast = React.forwardRef< const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>, React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => { >(({ className, variant, ...props }, ref) => {
return ( return (
<ToastPrimitives.Root <ToastPrimitives.Root
@@ -59,7 +58,7 @@ const ToastAction = React.forwardRef<
<ToastPrimitives.Action <ToastPrimitives.Action
ref={ref} ref={ref}
className={cn( className={cn(
"hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:ring-1 focus:outline-none disabled:pointer-events-none disabled:opacity-50", 'hover:bg-secondary focus:ring-ring group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors focus:ring-1 focus:outline-none disabled:pointer-events-none disabled:opacity-50',
className, className,
)} )}
{...props} {...props}
@@ -74,7 +73,7 @@ const ToastClose = React.forwardRef<
<ToastPrimitives.Close <ToastPrimitives.Close
ref={ref} ref={ref}
className={cn( className={cn(
"text-foreground/50 hover:text-foreground absolute top-1 right-1 rounded-md p-1 opacity-0 transition-opacity group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 focus:opacity-100 focus:ring-1 focus:outline-none group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", 'text-foreground/50 hover:text-foreground absolute top-1 right-1 rounded-md p-1 opacity-0 transition-opacity group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 focus:opacity-100 focus:ring-1 focus:outline-none group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className, className,
)} )}
toast-close="" toast-close=""
@@ -91,7 +90,7 @@ const ToastTitle = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Title <ToastPrimitives.Title
ref={ref} ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)} className={cn('text-sm font-semibold [&+div]:text-xs', className)}
{...props} {...props}
/> />
)); ));
@@ -103,7 +102,7 @@ const ToastDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<ToastPrimitives.Description <ToastPrimitives.Description
ref={ref} ref={ref}
className={cn("text-sm opacity-90", className)} className={cn('text-sm opacity-90', className)}
{...props} {...props}
/> />
)); ));
+23 -25
View File
@@ -1,5 +1,5 @@
import * as React from "react"; import * as React from 'react';
import { Toast, ToastActionElement, ToastProps } from "@/components/ui/toast"; import { Toast, ToastActionElement, ToastProps } from '@/components/ui/toast';
const TOAST_LIMIT = 1; const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000; const TOAST_REMOVE_DELAY = 1000000;
@@ -12,10 +12,10 @@ type ToasterToast = ToastProps & {
}; };
const actionTypes = { const actionTypes = {
ADD_TOAST: "ADD_TOAST", ADD_TOAST: 'ADD_TOAST',
UPDATE_TOAST: "UPDATE_TOAST", UPDATE_TOAST: 'UPDATE_TOAST',
DISMISS_TOAST: "DISMISS_TOAST", DISMISS_TOAST: 'DISMISS_TOAST',
REMOVE_TOAST: "REMOVE_TOAST", REMOVE_TOAST: 'REMOVE_TOAST',
} as const; } as const;
let count = 0; let count = 0;
@@ -29,20 +29,20 @@ type ActionType = typeof actionTypes;
type Action = type Action =
| { | {
type: ActionType["ADD_TOAST"]; type: ActionType['ADD_TOAST'];
toast: ToasterToast; toast: ToasterToast;
} }
| { | {
type: ActionType["UPDATE_TOAST"]; type: ActionType['UPDATE_TOAST'];
toast: Partial<ToasterToast>; toast: Partial<ToasterToast>;
} }
| { | {
type: ActionType["DISMISS_TOAST"]; type: ActionType['DISMISS_TOAST'];
toastId?: ToasterToast["id"]; toastId?: ToasterToast['id'];
} }
| { | {
type: ActionType["REMOVE_TOAST"]; type: ActionType['REMOVE_TOAST'];
toastId?: ToasterToast["id"]; toastId?: ToasterToast['id'];
}; };
interface State { interface State {
@@ -59,7 +59,7 @@ const addToRemoveQueue = (toastId: string) => {
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
toastTimeouts.delete(toastId); toastTimeouts.delete(toastId);
dispatch({ dispatch({
type: "REMOVE_TOAST", type: 'REMOVE_TOAST',
toastId, toastId,
}); });
}, TOAST_REMOVE_DELAY); }, TOAST_REMOVE_DELAY);
@@ -69,21 +69,19 @@ const addToRemoveQueue = (toastId: string) => {
export const reducer = (state: State, action: Action): State => { export const reducer = (state: State, action: Action): State => {
switch (action.type) { switch (action.type) {
case "ADD_TOAST": case 'ADD_TOAST':
return { return {
...state, ...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}; };
case "UPDATE_TOAST": case 'UPDATE_TOAST':
return { return {
...state, ...state,
toasts: state.toasts.map((t) => toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
t.id === action.toast.id ? { ...t, ...action.toast } : t,
),
}; };
case "DISMISS_TOAST": { case 'DISMISS_TOAST': {
const { toastId } = action; const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action, // ! Side effects ! - This could be extracted into a dismissToast() action,
@@ -108,7 +106,7 @@ export const reducer = (state: State, action: Action): State => {
), ),
}; };
} }
case "REMOVE_TOAST": case 'REMOVE_TOAST':
if (action.toastId === undefined) { if (action.toastId === undefined) {
return { return {
...state, ...state,
@@ -133,20 +131,20 @@ function dispatch(action: Action) {
}); });
} }
type Toast = Omit<ToasterToast, "id">; type Toast = Omit<ToasterToast, 'id'>;
function toast({ ...props }: Toast) { function toast({ ...props }: Toast) {
const id = genId(); const id = genId();
const update = (props: ToasterToast) => const update = (props: ToasterToast) =>
dispatch({ dispatch({
type: "UPDATE_TOAST", type: 'UPDATE_TOAST',
toast: { ...props, id }, toast: { ...props, id },
}); });
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
dispatch({ dispatch({
type: "ADD_TOAST", type: 'ADD_TOAST',
toast: { toast: {
...props, ...props,
id, id,
@@ -180,7 +178,7 @@ function useToast() {
return { return {
...state, ...state,
toast, toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
}; };
} }
+4 -9
View File
@@ -1,12 +1,7 @@
import { z } from "zod"; import { z } from 'zod';
import { os } from "@orpc/server"; import { os } from '@orpc/server';
import { import { listAccountsData, addAccountSnapshot, switchAccount, deleteAccount } from './handler';
listAccountsData, import { AccountSchema } from '../../types/account';
addAccountSnapshot,
switchAccount,
deleteAccount,
} from "./handler";
import { AccountSchema } from "../../types/account";
export const accountRouter = os.router({ export const accountRouter = os.router({
listAccounts: os.output(z.array(AccountSchema)).handler(async () => { listAccounts: os.output(z.array(AccountSchema)).handler(async () => {
+2 -2
View File
@@ -1,5 +1,5 @@
import { os } from "@orpc/server"; import { os } from '@orpc/server';
import { app } from "electron"; import { app } from 'electron';
export const currentPlatfom = os.handler(() => { export const currentPlatfom = os.handler(() => {
return process.platform; return process.platform;
+4 -12
View File
@@ -1,15 +1,7 @@
import { z } from "zod"; import { z } from 'zod';
import { os } from "@orpc/server"; import { os } from '@orpc/server';
import { import { backupAccount, restoreAccount, getCurrentAccountInfo } from './handler';
backupAccount, import { AccountBackupDataSchema, AccountInfoSchema, AccountSchema } from '../../types/account';
restoreAccount,
getCurrentAccountInfo,
} from "./handler";
import {
AccountBackupDataSchema,
AccountInfoSchema,
AccountSchema,
} from "../../types/account";
export const databaseRouter = os.router({ export const databaseRouter = os.router({
backupAccount: os backupAccount: os
+12 -14
View File
@@ -1,6 +1,6 @@
import { os } from "@orpc/server"; import { os } from '@orpc/server';
import { nativeTheme } from "electron"; import { nativeTheme } from 'electron';
import { setThemeModeInputSchema } from "./schemas"; import { setThemeModeInputSchema } from './schemas';
export const getCurrentThemeMode = os.handler(() => { export const getCurrentThemeMode = os.handler(() => {
return nativeTheme.themeSource; return nativeTheme.themeSource;
@@ -8,26 +8,24 @@ export const getCurrentThemeMode = os.handler(() => {
export const toggleThemeMode = os.handler(() => { export const toggleThemeMode = os.handler(() => {
if (nativeTheme.shouldUseDarkColors) { if (nativeTheme.shouldUseDarkColors) {
nativeTheme.themeSource = "light"; nativeTheme.themeSource = 'light';
} else { } else {
nativeTheme.themeSource = "dark"; nativeTheme.themeSource = 'dark';
} }
return nativeTheme.shouldUseDarkColors; return nativeTheme.shouldUseDarkColors;
}); });
export const setThemeMode = os export const setThemeMode = os.input(setThemeModeInputSchema).handler(({ input: mode }) => {
.input(setThemeModeInputSchema)
.handler(({ input: mode }) => {
switch (mode) { switch (mode) {
case "light": case 'light':
nativeTheme.themeSource = "light"; nativeTheme.themeSource = 'light';
break; break;
case "dark": case 'dark':
nativeTheme.themeSource = "dark"; nativeTheme.themeSource = 'dark';
break; break;
case "system": case 'system':
nativeTheme.themeSource = "system"; nativeTheme.themeSource = 'system';
break; break;
} }
+2 -2
View File
@@ -1,3 +1,3 @@
import { z } from "zod"; import { z } from 'zod';
export const setThemeModeInputSchema = z.enum(["light", "dark", "system"]); export const setThemeModeInputSchema = z.enum(['light', 'dark', 'system']);
+6 -13
View File
@@ -1,18 +1,14 @@
import { os } from "@orpc/server"; import { os } from '@orpc/server';
import { ipcContext } from "../context"; import { ipcContext } from '../context';
import { logger } from "../../utils/logger"; import { logger } from '../../utils/logger';
export const minimizeWindow = os export const minimizeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => {
.use(ipcContext.mainWindowContext)
.handler(({ context }) => {
const { window } = context; const { window } = context;
window.minimize(); window.minimize();
}); });
export const maximizeWindow = os export const maximizeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => {
.use(ipcContext.mainWindowContext)
.handler(({ context }) => {
const { window } = context; const { window } = context;
if (window.isMaximized()) { if (window.isMaximized()) {
@@ -22,12 +18,9 @@ export const maximizeWindow = os
} }
}); });
export const closeWindow = os export const closeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => {
.use(ipcContext.mainWindowContext)
.handler(({ context }) => {
logger.info('IPC: closeWindow called'); logger.info('IPC: closeWindow called');
const { window } = context; const { window } = context;
window.close(); window.close();
}); });
+2 -2
View File
@@ -1,5 +1,5 @@
import { type ClassValue, clsx } from "clsx"; import { type ClassValue, clsx } from 'clsx';
import { twMerge } from "tailwind-merge"; import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
+1 -1
View File
@@ -1 +1 @@
import "@/App"; import '@/App';
+2 -2
View File
@@ -1,5 +1,5 @@
import { MainLayout } from "@/layouts/MainLayout"; import { MainLayout } from '@/layouts/MainLayout';
import { createRootRoute } from "@tanstack/react-router"; import { createRootRoute } from '@tanstack/react-router';
/* import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' */ /* import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' */
/* /*
+15 -16
View File
@@ -1,6 +1,6 @@
@import "tailwindcss"; @import 'tailwindcss';
@plugin "tailwindcss-animate"; @plugin 'tailwindcss-animate';
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@@ -32,7 +32,7 @@
--color-card: hsl(var(--card)); --color-card: hsl(var(--card));
--color-card-foreground: hsl(var(--card-foreground)); --color-card-foreground: hsl(var(--card-foreground));
--font-sans: Geist, "Microsoft YaHei", "Heiti SC", "WenQuanYi Micro Hei", sans-serif; --font-sans: Geist, 'Microsoft YaHei', 'Heiti SC', 'WenQuanYi Micro Hei', sans-serif;
--font-mono: Geist Mono, monospace; --font-mono: Geist Mono, monospace;
--font-tomorrow: Tomorrow, sans-serif; --font-tomorrow: Tomorrow, sans-serif;
@@ -74,45 +74,44 @@
@layer base { @layer base {
@font-face { @font-face {
font-family: "Geist"; font-family: 'Geist';
src: url("../assets/fonts/geist/geist.ttf") format("truetype"); src: url('../assets/fonts/geist/geist.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Geist Mono"; font-family: 'Geist Mono';
font-display: swap; font-display: swap;
src: url("../assets/fonts/geist-mono/geist-mono.ttf") format("truetype"); src: url('../assets/fonts/geist-mono/geist-mono.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Tomorrow"; font-family: 'Tomorrow';
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
src: url("../assets/fonts/tomorrow/tomorrow-regular.ttf") format("truetype"); src: url('../assets/fonts/tomorrow/tomorrow-regular.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Tomorrow"; font-family: 'Tomorrow';
font-weight: 400; font-weight: 400;
font-style: italic; font-style: italic;
src: url("../assets/fonts/tomorrow/tomorrow-italic.ttf") format("truetype"); src: url('../assets/fonts/tomorrow/tomorrow-italic.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Tomorrow"; font-family: 'Tomorrow';
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
src: url("../assets/fonts/tomorrow/tomorrow-bold.ttf") format("truetype"); src: url('../assets/fonts/tomorrow/tomorrow-bold.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: "Tomorrow"; font-family: 'Tomorrow';
font-weight: 700; font-weight: 700;
font-style: italic; font-style: italic;
src: url("../assets/fonts/tomorrow/tomorrow-bold-italic.ttf") src: url('../assets/fonts/tomorrow/tomorrow-bold-italic.ttf') format('truetype');
format("truetype");
} }
} }
+23 -23
View File
@@ -1,45 +1,45 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { import {
listAccountsData, listAccountsData,
addAccountSnapshot, addAccountSnapshot,
switchAccount, switchAccount,
deleteAccount, deleteAccount,
} from "../../ipc/account/handler"; } from '../../ipc/account/handler';
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
// Mock dependencies // Mock dependencies
vi.mock("../../utils/paths", async () => { vi.mock('../../utils/paths', async () => {
const path = await import("path"); const path = await import('path');
const agentDir = path.join(process.cwd(), "temp_test_agent"); const agentDir = path.join(process.cwd(), 'temp_test_agent');
return { return {
getAgentDir: vi.fn(() => agentDir), getAgentDir: vi.fn(() => agentDir),
getAccountsFilePath: vi.fn(() => path.join(agentDir, "accounts.json")), getAccountsFilePath: vi.fn(() => path.join(agentDir, 'accounts.json')),
getBackupsDir: vi.fn(() => path.join(agentDir, "backups")), getBackupsDir: vi.fn(() => path.join(agentDir, 'backups')),
getAntigravityDbPath: vi.fn(() => path.join(agentDir, "state.vscdb")), getAntigravityDbPath: vi.fn(() => path.join(agentDir, 'state.vscdb')),
getAntigravityExecutablePath: vi.fn(() => "mock_exec_path"), getAntigravityExecutablePath: vi.fn(() => 'mock_exec_path'),
}; };
}); });
vi.mock("../../ipc/database/handler", () => ({ vi.mock('../../ipc/database/handler', () => ({
getCurrentAccountInfo: vi.fn(() => ({ getCurrentAccountInfo: vi.fn(() => ({
email: "[email protected]", email: '[email protected]',
name: "Test User", name: 'Test User',
isAuthenticated: true, isAuthenticated: true,
})), })),
backupAccount: vi.fn((account) => ({ version: "1.0", account, data: {} })), backupAccount: vi.fn((account) => ({ version: '1.0', account, data: {} })),
restoreAccount: vi.fn(), restoreAccount: vi.fn(),
getDatabaseConnection: vi.fn(), getDatabaseConnection: vi.fn(),
})); }));
vi.mock("../../ipc/process/handler", () => ({ vi.mock('../../ipc/process/handler', () => ({
closeAntigravity: vi.fn(), closeAntigravity: vi.fn(),
startAntigravity: vi.fn(), startAntigravity: vi.fn(),
isProcessRunning: vi.fn(() => Promise.resolve(false)), isProcessRunning: vi.fn(() => Promise.resolve(false)),
})); }));
describe("Account Handler", () => { describe('Account Handler', () => {
const testAgentDir = path.join(process.cwd(), "temp_test_agent"); const testAgentDir = path.join(process.cwd(), 'temp_test_agent');
beforeEach(() => { beforeEach(() => {
if (fs.existsSync(testAgentDir)) { if (fs.existsSync(testAgentDir)) {
@@ -54,22 +54,22 @@ describe("Account Handler", () => {
} }
}); });
it("should add account snapshot", async () => { it('should add account snapshot', async () => {
const account = await addAccountSnapshot(); const account = await addAccountSnapshot();
expect(account.email).toBe("[email protected]"); expect(account.email).toBe('[email protected]');
const accounts = await listAccountsData(); const accounts = await listAccountsData();
expect(accounts).toHaveLength(1); expect(accounts).toHaveLength(1);
expect(accounts[0].email).toBe("[email protected]"); expect(accounts[0].email).toBe('[email protected]');
}); });
it("should switch account", async () => { it('should switch account', async () => {
const account = await addAccountSnapshot(); const account = await addAccountSnapshot();
await switchAccount(account.id); await switchAccount(account.id);
// Mocks should be called, verified by implementation logic not throwing // Mocks should be called, verified by implementation logic not throwing
}); });
it("should delete account", async () => { it('should delete account', async () => {
const account = await addAccountSnapshot(); const account = await addAccountSnapshot();
await deleteAccount(account.id); await deleteAccount(account.id);
+22 -22
View File
@@ -1,26 +1,26 @@
import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { logger } from "../../utils/logger"; import { logger } from '../../utils/logger';
import fs from "fs"; import fs from 'fs';
import path from "path"; import path from 'path';
vi.mock("../../utils/paths", async () => { vi.mock('../../utils/paths', async () => {
const path = await import("path"); const path = await import('path');
return { return {
getAgentDir: vi.fn(() => path.join(process.cwd(), "temp_test_logs")), getAgentDir: vi.fn(() => path.join(process.cwd(), 'temp_test_logs')),
}; };
}); });
describe("Logger Utilities", () => { describe('Logger Utilities', () => {
const testLogDir = path.join(process.cwd(), "temp_test_logs"); const testLogDir = path.join(process.cwd(), 'temp_test_logs');
const testLogFile = path.join(testLogDir, "app.log"); const testLogFile = path.join(testLogDir, 'app.log');
beforeEach(() => { beforeEach(() => {
console.log("Test Log Dir:", testLogDir); console.log('Test Log Dir:', testLogDir);
if (fs.existsSync(testLogDir)) { if (fs.existsSync(testLogDir)) {
fs.rmSync(testLogDir, { recursive: true, force: true }); fs.rmSync(testLogDir, { recursive: true, force: true });
} }
fs.mkdirSync(testLogDir, { recursive: true }); fs.mkdirSync(testLogDir, { recursive: true });
console.log("Dir created:", fs.existsSync(testLogDir)); console.log('Dir created:', fs.existsSync(testLogDir));
}); });
afterEach(() => { afterEach(() => {
@@ -29,24 +29,24 @@ describe("Logger Utilities", () => {
} }
}); });
it("should create log file if it does not exist", () => { it('should create log file if it does not exist', () => {
logger.info("Test message"); logger.info('Test message');
expect(fs.existsSync(testLogFile)).toBe(true); expect(fs.existsSync(testLogFile)).toBe(true);
}); });
it("should write formatted message to file", () => { it('should write formatted message to file', () => {
const message = "Test info message"; const message = 'Test info message';
logger.info(message); logger.info(message);
const content = fs.readFileSync(testLogFile, "utf-8"); const content = fs.readFileSync(testLogFile, 'utf-8');
expect(content).toContain("[INFO]"); expect(content).toContain('[INFO]');
expect(content).toContain(message); expect(content).toContain(message);
}); });
it("should log error messages", () => { it('should log error messages', () => {
const message = "Test error message"; const message = 'Test error message';
logger.error(message); logger.error(message);
const content = fs.readFileSync(testLogFile, "utf-8"); const content = fs.readFileSync(testLogFile, 'utf-8');
expect(content).toContain("[ERROR]"); expect(content).toContain('[ERROR]');
expect(content).toContain(message); expect(content).toContain(message);
}); });
}); });
+12 -14
View File
@@ -1,29 +1,27 @@
import { describe, it, expect, vi } from "vitest"; import { describe, it, expect, vi } from 'vitest';
import { import { isProcessRunning, closeAntigravity, startAntigravity } from '../../ipc/process/handler';
isProcessRunning, import { exec } from 'child_process';
closeAntigravity,
startAntigravity,
} from "../../ipc/process/handler";
import { exec } from "child_process";
vi.mock("child_process", () => { vi.mock('child_process', () => {
return { return {
exec: vi.fn(), exec: vi.fn(),
default: { exec: vi.fn() },
}; };
}); });
// Mock promisify to return a function that returns a promise // Mock promisify to return a function that returns a promise
vi.mock("util", () => ({ vi.mock('util', () => ({
promisify: (fn: unknown) => fn, promisify: (fn: unknown) => fn,
default: { promisify: (fn: unknown) => fn },
})); }));
describe("Process Handler", () => { describe('Process Handler', () => {
it("should check if process is running", async () => { it('should check if process is running', async () => {
// Mock exec to return stdout // Mock exec to return stdout
const execMock = exec as unknown as ReturnType<typeof vi.fn>; const execMock = exec as unknown as ReturnType<typeof vi.fn>;
execMock.mockImplementation((cmd, cb) => { execMock.mockImplementation((cmd, cb) => {
if (cb) cb(null, { stdout: "12345" }, { stderr: "" }); if (cb) cb(null, { stdout: '12345' }, { stderr: '' });
return { stdout: "12345" }; return { stdout: '12345' };
}); });
// Wait, promisify wraps exec. // Wait, promisify wraps exec.
@@ -49,7 +47,7 @@ describe("Process Handler", () => {
// Skip complex mocking tests for now as they are fragile without proper setup. // Skip complex mocking tests for now as they are fragile without proper setup.
// I'll write a basic test structure. // I'll write a basic test structure.
it("should be defined", () => { it('should be defined', () => {
expect(isProcessRunning).toBeDefined(); expect(isProcessRunning).toBeDefined();
expect(closeAntigravity).toBeDefined(); expect(closeAntigravity).toBeDefined();
expect(startAntigravity).toBeDefined(); expect(startAntigravity).toBeDefined();
+11 -11
View File
@@ -1,27 +1,27 @@
// @vitest-environment happy-dom // @vitest-environment happy-dom
import { render } from "@testing-library/react"; import { render } from '@testing-library/react';
import { test, expect } from "vitest"; import { test, expect } from 'vitest';
import ToggleTheme from "@/components/toggle-theme"; import ToggleTheme from '@/components/toggle-theme';
test("renders ToggleTheme", () => { test('renders ToggleTheme', () => {
const { getByRole } = render(<ToggleTheme />); const { getByRole } = render(<ToggleTheme />);
const isButton = getByRole("button"); const isButton = getByRole('button');
expect(isButton).toBeInTheDocument(); expect(isButton).toBeInTheDocument();
}); });
test("has icon", () => { test('has icon', () => {
const { getByRole } = render(<ToggleTheme />); const { getByRole } = render(<ToggleTheme />);
const button = getByRole("button"); const button = getByRole('button');
const icon = button.querySelector("svg"); const icon = button.querySelector('svg');
expect(icon).toBeInTheDocument(); expect(icon).toBeInTheDocument();
}); });
test("is moon icon", () => { test('is moon icon', () => {
const svgIconClassName: string = "lucide-moon"; const svgIconClassName: string = 'lucide-moon';
const { getByRole } = render(<ToggleTheme />); const { getByRole } = render(<ToggleTheme />);
const svg = getByRole("button").querySelector("svg"); const svg = getByRole('button').querySelector('svg');
expect(svg?.classList).toContain(svgIconClassName); expect(svg?.classList).toContain(svgIconClassName);
}); });
+1 -1
View File
@@ -1 +1 @@
export type ThemeMode = "dark" | "light" | "system"; export type ThemeMode = 'dark' | 'light' | 'system';
+4 -4
View File
@@ -1,7 +1,7 @@
import { routeTree } from "@/routeTree.gen"; import { routeTree } from '@/routeTree.gen';
import { createMemoryHistory, createRouter } from "@tanstack/react-router"; import { createMemoryHistory, createRouter } from '@tanstack/react-router';
declare module "@tanstack/react-router" { declare module '@tanstack/react-router' {
interface Register { interface Register {
router: typeof router; router: typeof router;
} }
@@ -11,6 +11,6 @@ export const router = createRouter({
defaultPendingMinMs: 0, defaultPendingMinMs: 0,
routeTree, routeTree,
history: createMemoryHistory({ history: createMemoryHistory({
initialEntries: ["/"], initialEntries: ['/'],
}), }),
}); });
-1
View File
@@ -1,4 +1,3 @@
import { exec } from 'child_process'; import { exec } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import fs from 'fs'; import fs from 'fs';
+1 -1
View File
@@ -1,4 +1,4 @@
import { defineConfig } from "vite"; import { defineConfig } from 'vite';
// https://vitejs.dev/config // https://vitejs.dev/config
export default defineConfig({}); export default defineConfig({});