Files
signed/crates/settings/src/settings.rs
T
reya fa50e9f153
Rust / build (macos-latest, stable) (push) Canceled after 0s
Rust / build (ubuntu-latest, stable) (push) Canceled after 0s
Rust / build (windows-latest, stable) (push) Canceled after 0s
feat: add event fetching strategy (#24)
Reviewed-on: #24
2026-09-27 02:50:11 +00:00

209 lines
6.5 KiB
Rust

use std::path::PathBuf;
use serde::{Deserialize, Serialize};
/// The default grasp servers, offered while the user has not published a grasp list.
pub const DEFAULT_GRASP_SERVERS: [&str; 3] = [
"wss://relay.ngit.dev",
"wss://gitnostr.com",
"wss://git.shakespeare.diy",
];
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AppearanceMode {
#[default]
System,
Light,
Dark,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventFetchingStrategy {
/// Only the relays declared in the repository announcement.
Curated,
/// Repository relays plus every maintainer's NIP-65 relays.
#[default]
Uncensored,
}
/// Fields mirror the gpui-component `Theme` surface customized at startup.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct ThemeSettings {
/// Name of the light theme in the theme registry.
pub light_theme: String,
/// Name of the dark theme in the theme registry.
pub dark_theme: String,
/// The base font size in pixels.
pub font_size: f32,
/// The monospace font size in pixels.
pub mono_font_size: f32,
/// Corner radius for general elements in pixels.
pub radius: f32,
/// Corner radius for large elements, dialogs and notifications, in pixels.
pub radius_lg: f32,
/// Whether focused controls draw a ring outside their border.
pub focus_ring: bool,
pub shadow: bool,
}
impl Default for ThemeSettings {
fn default() -> Self {
Self {
light_theme: "Signed Light".into(),
dark_theme: "Signed Dark".into(),
font_size: 16.0,
mono_font_size: 13.0,
radius: 2.0,
radius_lg: 6.0,
focus_ring: false,
shadow: false,
}
}
}
/// Tab bar chrome.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct TabBarSettings {
/// Hide the previous and next tab buttons.
pub hide_navigation_buttons: bool,
}
impl Default for TabBarSettings {
fn default() -> Self {
Self {
hide_navigation_buttons: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct GraspServersSettings {
/// Servers offered while the user has not published a grasp list, kind `10317`.
pub default_servers: Vec<String>,
}
impl Default for GraspServersSettings {
fn default() -> Self {
Self {
default_servers: DEFAULT_GRASP_SERVERS
.iter()
.map(|server| (*server).to_owned())
.collect(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct LocalReposSettings {
/// The directories scanned for local git repositories,
/// defaults to the user's Desktop and Documents folders.
pub scan_paths: Vec<PathBuf>,
}
fn default_scan_paths() -> Vec<PathBuf> {
vec![paths::desktop_dir(), paths::documents_dir()]
}
impl Default for LocalReposSettings {
fn default() -> Self {
Self {
scan_paths: default_scan_paths(),
}
}
}
/// A remembered association between a local checkout folder and an announced repository,
/// recorded when the user clones a repository or picks a folder in the New PR panel.
///
/// The panel can then prefill the folder later without asking again.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct CheckoutRecord {
pub path: PathBuf,
/// Repository address as a string, `30617:<pubkey>:<id>`.
pub addr: String,
/// Unix seconds of the last use, for freshest-first ordering.
pub last_used: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct CheckoutsSettings {
/// The latest use of a path and repo pair replaces the older record.
pub records: Vec<CheckoutRecord>,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct CreateRepositorySettings {
/// The folder the create-repository dialog defaults to, the user's Desktop when unset.
pub default_folder: Option<PathBuf>,
}
/// The complete set of persisted application settings.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
pub struct Settings {
pub appearance: AppearanceMode,
pub event_fetching: EventFetchingStrategy,
pub theme: ThemeSettings,
pub tab_bar: TabBarSettings,
pub grasp_servers: GraspServersSettings,
pub local_repos: LocalReposSettings,
pub checkouts: CheckoutsSettings,
pub create_repository: CreateRepositorySettings,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn json_roundtrip_preserves_everything() {
let settings = Settings {
appearance: AppearanceMode::Dark,
event_fetching: EventFetchingStrategy::Curated,
theme: ThemeSettings {
radius: 8.0,
..Default::default()
},
create_repository: CreateRepositorySettings {
default_folder: Some(PathBuf::from("/tmp/repos")),
},
..Default::default()
};
let json = serde_json::to_string(&settings).unwrap();
let parsed: Settings = serde_json::from_str(&json).unwrap();
assert_eq!(parsed, settings);
}
#[test]
fn partial_json_merges_with_defaults() {
let settings: Settings =
serde_json::from_str(r#"{"appearance": "dark", "theme": {"radius": 4.0}}"#).unwrap();
assert_eq!(settings.appearance, AppearanceMode::Dark);
assert_eq!(settings.theme.radius, 4.0);
// Unset fields fall back to their defaults, including the fetching strategy.
assert_eq!(settings.event_fetching, EventFetchingStrategy::Uncensored);
// The rest of the theme and the other groups keep their defaults.
assert_eq!(settings.theme.light_theme, "Signed Light");
assert_eq!(settings.grasp_servers, GraspServersSettings::default());
assert_eq!(settings.create_repository.default_folder, None);
}
#[test]
fn event_fetching_uses_snake_case() {
let json = serde_json::to_string(&EventFetchingStrategy::Curated).unwrap();
assert_eq!(json, r#""curated""#);
let parsed: EventFetchingStrategy = serde_json::from_str(r#""uncensored""#).unwrap();
assert_eq!(parsed, EventFetchingStrategy::Uncensored);
}
}