diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2fc5027 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Xyroscar + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 858d179..27514e4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,67 @@ -# Tauri + SvelteKit + TypeScript +# Resona -This template should help get you started developing with Tauri, SvelteKit and TypeScript in Vite. +Resona is a desktop API client for testing and debugging HTTP APIs. Built with Tauri, SvelteKit, and Rust. -## Recommended IDE Setup +## Features -[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer). +- **Workspaces**: Organize your API requests into workspaces with tags for easy filtering +- **Collections**: Group related requests into collections within workspaces +- **Variables**: Define variables at global, workspace, collection, or request scope with automatic interpolation +- **HTTP Client**: Send HTTP requests with support for various body types (JSON, form-data, URL-encoded, etc.) +- **Sync Groups**: Sync variables across multiple workspaces +- **Themes**: Multiple built-in themes including light, dark, and Catppuccin variants (Latte, Frappe, Macchiato, Mocha) +- **Persistent Storage**: All data is stored locally using an embedded database (redb) + +## Tech Stack + +- **Frontend**: SvelteKit, TypeScript, TailwindCSS, shadcn-svelte +- **Backend**: Rust, Tauri +- **Database**: redb (embedded key-value store) +- **HTTP**: reqwest + +## Development + +### Prerequisites + +- Node.js (v18+) +- Rust (latest stable) +- Bun (or npm/yarn/pnpm) + +### Setup + +```bash +# Install dependencies +bun install + +# Run in development mode +bun run tauri dev + +# Build for production +bun run tauri build +``` + +### Project Structure + +``` +resona/ +├── src/ # Frontend (SvelteKit) +│ ├── lib/ +│ │ ├── components/ # UI components +│ │ ├── services/ # API service layer +│ │ └── types/ # TypeScript types +│ └── routes/ # SvelteKit routes +├── src-tauri/ # Backend (Rust/Tauri) +│ └── src/ +│ ├── collections/ # Collections module +│ ├── db/ # Database layer +│ ├── http/ # HTTP client +│ ├── requests/ # Requests module +│ ├── settings/ # App settings +│ ├── variables/ # Variables module +│ └── workspaces/ # Workspaces module +└── static/ # Static assets +``` + +## License + +MIT License - see [LICENSE](LICENSE) for details. diff --git a/src-tauri/src/db/error.rs b/src-tauri/src/db/error.rs index 624bad2..e0de9f8 100644 --- a/src-tauri/src/db/error.rs +++ b/src-tauri/src/db/error.rs @@ -25,6 +25,9 @@ pub enum DbError { #[error("Serialization error: {0}")] Serialization(String), + #[error("JSON error: {0}")] + Json(#[from] serde_json::Error), + #[error("IO error: {0}")] Io(#[from] std::io::Error), } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 996381f..9cc5bab 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -4,6 +4,7 @@ mod collections; mod db; mod http; mod requests; +mod settings; mod variables; mod workspaces; @@ -18,6 +19,7 @@ use requests::{ create_request, delete_request, get_all_requests_by_workspace, get_request, get_requests_by_collection, get_standalone_requests_by_workspace, update_request, }; +use settings::{get_settings, reset_settings, update_settings}; use variables::{ create_variable, delete_variable, get_collection_variables, get_global_variables, get_request_variables, get_resolved_variables, get_variable, get_workspace_variables, @@ -86,6 +88,10 @@ pub fn run() { delete_variable, // HTTP client send_http_request, + // Settings commands + get_settings, + update_settings, + reset_settings, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/settings/commands.rs b/src-tauri/src/settings/commands.rs new file mode 100644 index 0000000..0e1fc1a --- /dev/null +++ b/src-tauri/src/settings/commands.rs @@ -0,0 +1,27 @@ +use tauri::State; + +use crate::db::Database; + +use super::service::SettingsService; +use super::types::{AppSettings, UpdateSettingsInput}; + +#[tauri::command] +pub fn get_settings(db: State) -> Result { + let service = SettingsService::new(db.inner().clone()); + service.get().map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn update_settings( + db: State, + input: UpdateSettingsInput, +) -> Result { + let service = SettingsService::new(db.inner().clone()); + service.update(input).map_err(|e| e.to_string()) +} + +#[tauri::command] +pub fn reset_settings(db: State) -> Result { + let service = SettingsService::new(db.inner().clone()); + service.reset().map_err(|e| e.to_string()) +} diff --git a/src-tauri/src/settings/mod.rs b/src-tauri/src/settings/mod.rs new file mode 100644 index 0000000..3782304 --- /dev/null +++ b/src-tauri/src/settings/mod.rs @@ -0,0 +1,7 @@ +mod commands; +mod service; +mod types; + +pub use commands::*; +#[allow(unused_imports)] +pub use types::{AppSettings, CustomTheme, Theme, ThemeColors, UpdateSettingsInput}; diff --git a/src-tauri/src/settings/service.rs b/src-tauri/src/settings/service.rs new file mode 100644 index 0000000..0eb2bb5 --- /dev/null +++ b/src-tauri/src/settings/service.rs @@ -0,0 +1,78 @@ +use crate::db::{Database, DbResult, APP_SETTINGS}; + +use super::types::{AppSettings, UpdateSettingsInput}; + +const SETTINGS_KEY: &str = "settings"; + +pub struct SettingsService { + db: Database, +} + +impl SettingsService { + pub fn new(db: Database) -> Self { + Self { db } + } + + pub fn get(&self) -> DbResult { + let read_txn = self.db.begin_read()?; + let table = read_txn.open_table(APP_SETTINGS)?; + + match table.get(SETTINGS_KEY)? { + Some(value) => { + let settings: AppSettings = serde_json::from_str(value.value())?; + Ok(settings) + } + None => Ok(AppSettings::default()), + } + } + + pub fn update(&self, input: UpdateSettingsInput) -> DbResult { + let mut settings = self.get()?; + + if let Some(theme) = input.theme { + settings.theme = theme; + } + if let Some(custom_themes) = input.custom_themes { + settings.custom_themes = custom_themes; + } + if let Some(default_timeout) = input.default_timeout { + settings.default_timeout = default_timeout; + } + if let Some(follow_redirects) = input.follow_redirects { + settings.follow_redirects = follow_redirects; + } + if let Some(validate_ssl) = input.validate_ssl { + settings.validate_ssl = validate_ssl; + } + if let Some(max_history_items) = input.max_history_items { + settings.max_history_items = max_history_items; + } + if let Some(auto_save_requests) = input.auto_save_requests { + settings.auto_save_requests = auto_save_requests; + } + + let write_txn = self.db.begin_write()?; + { + let mut table = write_txn.open_table(APP_SETTINGS)?; + let json = serde_json::to_string(&settings)?; + table.insert(SETTINGS_KEY, json.as_str())?; + } + write_txn.commit()?; + + Ok(settings) + } + + pub fn reset(&self) -> DbResult { + let settings = AppSettings::default(); + + let write_txn = self.db.begin_write()?; + { + let mut table = write_txn.open_table(APP_SETTINGS)?; + let json = serde_json::to_string(&settings)?; + table.insert(SETTINGS_KEY, json.as_str())?; + } + write_txn.commit()?; + + Ok(settings) + } +} diff --git a/src-tauri/src/settings/types.rs b/src-tauri/src/settings/types.rs new file mode 100644 index 0000000..de88b27 --- /dev/null +++ b/src-tauri/src/settings/types.rs @@ -0,0 +1,95 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum Theme { + System, + Light, + Dark, + Latte, + Frappe, + Macchiato, + Mocha, + Custom, +} + +impl Default for Theme { + fn default() -> Self { + Self::System + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CustomTheme { + pub name: String, + pub colors: ThemeColors, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct ThemeColors { + pub background: String, + pub foreground: String, + pub primary: String, + pub secondary: String, + pub accent: String, + pub muted: String, + pub border: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSettings { + pub theme: Theme, + #[serde(default)] + pub custom_themes: Vec, + #[serde(default = "default_timeout")] + pub default_timeout: u32, + #[serde(default = "default_true")] + pub follow_redirects: bool, + #[serde(default = "default_true")] + pub validate_ssl: bool, + #[serde(default = "default_max_history")] + pub max_history_items: u32, + #[serde(default = "default_true")] + pub auto_save_requests: bool, +} + +fn default_timeout() -> u32 { + 30000 +} + +fn default_true() -> bool { + true +} + +fn default_max_history() -> u32 { + 100 +} + +impl Default for AppSettings { + fn default() -> Self { + Self { + theme: Theme::System, + custom_themes: Vec::new(), + default_timeout: 30000, + follow_redirects: true, + validate_ssl: true, + max_history_items: 100, + auto_save_requests: true, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateSettingsInput { + pub theme: Option, + pub custom_themes: Option>, + pub default_timeout: Option, + pub follow_redirects: Option, + pub validate_ssl: Option, + pub max_history_items: Option, + pub auto_save_requests: Option, +} diff --git a/src/lib/components/settings-dialog.svelte b/src/lib/components/settings-dialog.svelte index 98ab306..dc37dbe 100644 --- a/src/lib/components/settings-dialog.svelte +++ b/src/lib/components/settings-dialog.svelte @@ -7,12 +7,18 @@ import { Label } from "$lib/components/ui/label/index.js"; import { Separator } from "$lib/components/ui/separator/index.js"; import SettingsIcon from "@lucide/svelte/icons/settings"; - import type { AppSettings } from "$lib/types/workspace"; + import CheckIcon from "@lucide/svelte/icons/check"; + import type { AppSettings, Theme } from "$lib/types/workspace"; import { get_settings, update_settings, reset_settings, } from "$lib/services/settings"; + import { + theme as themeStore, + themes, + themeLabels, + } from "$lib/theme-switcher"; import { onMount } from "svelte"; type Props = { @@ -24,9 +30,10 @@ let settings = $state({ theme: "system", + customThemes: [], defaultTimeout: 30000, followRedirects: true, - validateSSL: true, + validateSsl: true, maxHistoryItems: 100, autoSaveRequests: true, }); @@ -56,10 +63,12 @@ async function handleReset() { settings = await reset_settings(); + themeStore.applyTheme(settings.theme); } - function handleThemeChange(value: string) { - settings.theme = value as "light" | "dark" | "system"; + async function handleThemeChange(newTheme: Theme) { + settings.theme = newTheme; + await themeStore.applyTheme(newTheme); } @@ -77,42 +86,69 @@ - - + + + Appearance General Requests Advanced
- -
- - - - {settings.theme === "light" - ? "Light" - : settings.theme === "dark" - ? "Dark" - : "System"} - - - System - Light - Dark - - + +
+

- Choose the application color theme. + Select a color theme for the application.

+
+ {#each themes as t} + + {/each} +
+
+

Theme Preview

+

+ Preview of the current theme colors. +

+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
@@ -208,15 +246,15 @@