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
+408 -485

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.
+14 -10
View File
@@ -52,20 +52,21 @@ 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 |
| `npm run test:unit` | Run unit tests with Vitest | | `npm run test:unit` | Run unit tests with Vitest |
| `npm run test:e2e` | Run E2E tests with Playwright | | `npm run test:e2e` | Run E2E tests with Playwright |
| `npm run test:all` | Run all tests | | `npm run test:all` | Run all tests |
| `npm run type-check` | Run TypeScript type checking | | `npm run type-check` | Run TypeScript type checking |
| `npm run make` | Build production packages | | `npm run make` | Build production packages |
## ✏️ 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
+22 -22
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>
--- ---
## 🚀 快速开始 ## 🚀 快速开始
@@ -183,11 +182,11 @@
从 [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) |
### 从源码构建 ### 从源码构建
@@ -217,14 +216,14 @@ 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/) |
| **状态管理** | [TanStack Query](https://tanstack.com/query/latest)、[TanStack Router](https://tanstack.com/router/latest) | | **状态管理** | [TanStack Query](https://tanstack.com/query/latest)、[TanStack Router](https://tanstack.com/router/latest) |
| **数据库** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) | | **数据库** | [Better-SQLite3](https://github.com/WiseLibs/better-sqlite3) |
| **测试** | [Vitest](https://vitest.dev/)、[Playwright](https://playwright.dev/) | | **测试** | [Vitest](https://vitest.dev/)、[Playwright](https://playwright.dev/) |
--- ---
@@ -232,16 +231,16 @@ 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 格式化代码 |
| `npm run test:unit` | 运行单元测试 | | `npm run test:unit` | 运行单元测试 |
| `npm run test:e2e` | 运行 E2E 测试 | | `npm run test:e2e` | 运行 E2E 测试 |
| `npm run test:all` | 运行所有测试 | | `npm run test:all` | 运行所有测试 |
| `npm run type-check` | TypeScript 类型检查 | | `npm run type-check` | TypeScript 类型检查 |
| `npm run make` | 构建生产包 | | `npm run make` | 构建生产包 |
### 项目结构 ### 项目结构
@@ -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` 后重新安装
+4 -4
View File
@@ -26,10 +26,10 @@ 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: |
We only provide security updates for the latest version. Please ensure you're running the most recent release. We only provide security updates for the latest version. Please ensure you're running the most recent release.
+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 };
+43 -70
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> <div
>(({ className, ...props }, ref) => ( ref={ref}
<div className={cn('bg-card text-card-foreground rounded-xl border shadow', className)}
ref={ref} {...props}
className={cn( />
"bg-card text-card-foreground rounded-xl border shadow", ),
className, );
)} Card.displayName = 'Card';
{...props}
/>
));
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> <h3
>(({ className, ...props }, ref) => ( ref={ref}
<h3 className={cn('leading-none font-semibold tracking-tight', className)}
ref={ref} {...props}
className={cn("leading-none font-semibold tracking-tight", className)} />
{...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
+19 -21
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,28 +8,26 @@ 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) switch (mode) {
.handler(({ input: mode }) => { case 'light':
switch (mode) { nativeTheme.themeSource = 'light';
case "light": break;
nativeTheme.themeSource = "light"; case 'dark':
break; nativeTheme.themeSource = 'dark';
case "dark": break;
nativeTheme.themeSource = "dark"; case 'system':
break; nativeTheme.themeSource = 'system';
case "system": break;
nativeTheme.themeSource = "system"; }
break;
}
return nativeTheme.themeSource; return nativeTheme.themeSource;
}); });
+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']);
+20 -27
View File
@@ -1,33 +1,26 @@
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) const { window } = context;
.handler(({ context }) => {
const { window } = context;
window.minimize(); window.minimize();
}); });
export const maximizeWindow = os export const maximizeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => {
.use(ipcContext.mainWindowContext) const { window } = context;
.handler(({ context }) => {
const { window } = context;
if (window.isMaximized()) { if (window.isMaximized()) {
window.unmaximize(); window.unmaximize();
} else { } else {
window.maximize(); window.maximize();
} }
}); });
export const closeWindow = os export const closeWindow = os.use(ipcContext.mainWindowContext).handler(({ context }) => {
.use(ipcContext.mainWindowContext) logger.info('IPC: closeWindow called');
.handler(({ context }) => { const { window } = context;
logger.info('IPC: closeWindow called');
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({});