278 lines
7.4 KiB
Rust
278 lines
7.4 KiB
Rust
use std::{
|
|
collections::{BTreeMap, BTreeSet},
|
|
path::PathBuf,
|
|
};
|
|
|
|
pub use gotcha_gitea::{
|
|
HistoryCommit, HomeData, IssueDetails, IssueEditorData, MilestoneDetails, Page, PullDetails,
|
|
civil_from_days, days_from_civil, parse_api_date, parse_api_timestamp,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum AppearanceMode {
|
|
Light,
|
|
Dark,
|
|
#[default]
|
|
#[serde(other)]
|
|
Auto,
|
|
}
|
|
|
|
impl AppearanceMode {
|
|
pub fn index(self) -> i32 {
|
|
match self {
|
|
Self::Auto => 0,
|
|
Self::Light => 1,
|
|
Self::Dark => 2,
|
|
}
|
|
}
|
|
|
|
pub fn from_index(index: i32) -> Option<Self> {
|
|
match index {
|
|
0 => Some(Self::Auto),
|
|
1 => Some(Self::Light),
|
|
2 => Some(Self::Dark),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Deserialize, Serialize)]
|
|
pub struct Server {
|
|
pub name: String,
|
|
pub url: String,
|
|
#[serde(default)]
|
|
pub provider: gotcha_gitea::Provider,
|
|
#[serde(default)]
|
|
pub credential_account: String,
|
|
#[serde(skip)]
|
|
pub token: String,
|
|
}
|
|
|
|
#[derive(Clone, Deserialize, Serialize)]
|
|
pub struct Preferences {
|
|
#[serde(skip)]
|
|
pub path: PathBuf,
|
|
#[serde(default)]
|
|
pub servers: Vec<Server>,
|
|
#[serde(default)]
|
|
pub favorites: BTreeSet<String>,
|
|
#[serde(default)]
|
|
pub last_server: Option<usize>,
|
|
#[serde(default = "open_status")]
|
|
pub issue_status: String,
|
|
#[serde(default)]
|
|
pub issue_filters: BTreeMap<String, IssueFilter>,
|
|
#[serde(default = "open_status")]
|
|
pub pull_status: String,
|
|
#[serde(default)]
|
|
pub pull_filters: BTreeMap<String, PullFilter>,
|
|
#[serde(default)]
|
|
pub appearance: AppearanceMode,
|
|
#[serde(default)]
|
|
pub notifications_enabled: bool,
|
|
#[serde(default)]
|
|
pub notification_cursors: BTreeMap<String, String>,
|
|
#[serde(default = "default_primary_destinations")]
|
|
pub primary_destinations: Vec<crate::PrimaryDestination>,
|
|
}
|
|
|
|
impl Default for Preferences {
|
|
fn default() -> Self {
|
|
Self {
|
|
path: PathBuf::new(),
|
|
servers: Vec::new(),
|
|
favorites: BTreeSet::new(),
|
|
last_server: None,
|
|
issue_status: open_status(),
|
|
issue_filters: BTreeMap::new(),
|
|
pull_status: open_status(),
|
|
pull_filters: BTreeMap::new(),
|
|
appearance: AppearanceMode::default(),
|
|
notifications_enabled: false,
|
|
notification_cursors: BTreeMap::new(),
|
|
primary_destinations: default_primary_destinations(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn default_primary_destinations() -> Vec<crate::PrimaryDestination> {
|
|
vec![
|
|
crate::PrimaryDestination::Issues,
|
|
crate::PrimaryDestination::Repositories,
|
|
crate::PrimaryDestination::PullRequests,
|
|
crate::PrimaryDestination::Milestones,
|
|
]
|
|
}
|
|
|
|
pub fn normalize_primary_destinations(destinations: &mut Vec<crate::PrimaryDestination>) -> bool {
|
|
let original = destinations.clone();
|
|
let mut unique = Vec::with_capacity(destinations.len().min(4));
|
|
for destination in destinations.drain(..) {
|
|
if !unique.contains(&destination) && unique.len() < 4 {
|
|
unique.push(destination);
|
|
}
|
|
}
|
|
*destinations = unique;
|
|
*destinations != original
|
|
}
|
|
|
|
#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub struct IssueFilter {
|
|
#[serde(default)]
|
|
pub milestone: String,
|
|
#[serde(default)]
|
|
pub labels: BTreeSet<String>,
|
|
#[serde(skip)]
|
|
pub search_text: String,
|
|
}
|
|
|
|
impl IssueFilter {
|
|
pub fn is_active(&self, status: &str) -> bool {
|
|
status != "open" || self != &Self::default()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize)]
|
|
pub struct PullFilter {
|
|
#[serde(default)]
|
|
pub milestone: String,
|
|
#[serde(skip)]
|
|
pub search_text: String,
|
|
}
|
|
|
|
impl PullFilter {
|
|
pub fn is_active(&self, status: &str) -> bool {
|
|
status != "open" || self != &Self::default()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct RepositoryData {
|
|
pub owner: String,
|
|
pub name: String,
|
|
pub description: String,
|
|
pub language: String,
|
|
pub open_issues: i64,
|
|
pub updated: String,
|
|
pub default_branch: String,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct State {
|
|
pub preferences: Preferences,
|
|
pub active_server: Option<usize>,
|
|
pub repositories: Vec<RepositoryData>,
|
|
}
|
|
|
|
pub struct IssueDraft {
|
|
pub title: String,
|
|
pub body: String,
|
|
pub label_ids: Vec<i64>,
|
|
pub milestone_id: Option<i64>,
|
|
pub due_date: Option<i64>,
|
|
pub closed: bool,
|
|
}
|
|
|
|
pub struct MilestoneDraft {
|
|
pub title: String,
|
|
pub description: String,
|
|
pub due_date: Option<i64>,
|
|
pub closed: bool,
|
|
}
|
|
|
|
pub fn open_status() -> String {
|
|
"open".into()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn issue_filter_default_state_is_owned_by_rust() {
|
|
assert!(!IssueFilter::default().is_active("open"));
|
|
assert!(IssueFilter::default().is_active("closed"));
|
|
assert!(
|
|
IssueFilter {
|
|
milestone: "v1".into(),
|
|
..Default::default()
|
|
}
|
|
.is_active("open")
|
|
);
|
|
assert!(!PullFilter::default().is_active("open"));
|
|
assert!(PullFilter::default().is_active("closed"));
|
|
assert!(
|
|
PullFilter {
|
|
milestone: "v1".into(),
|
|
..Default::default()
|
|
}
|
|
.is_active("open")
|
|
);
|
|
assert!(
|
|
IssueFilter {
|
|
search_text: "needle".into(),
|
|
..Default::default()
|
|
}
|
|
.is_active("open")
|
|
);
|
|
assert!(
|
|
PullFilter {
|
|
search_text: "needle".into(),
|
|
..Default::default()
|
|
}
|
|
.is_active("open")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn search_text_is_session_only() {
|
|
let issue = IssueFilter {
|
|
search_text: "needle".into(),
|
|
..Default::default()
|
|
};
|
|
let pull = PullFilter {
|
|
search_text: "needle".into(),
|
|
..Default::default()
|
|
};
|
|
|
|
assert!(!serde_json::to_string(&issue).unwrap().contains("needle"));
|
|
assert!(!serde_json::to_string(&pull).unwrap().contains("needle"));
|
|
assert_eq!(
|
|
serde_json::from_str::<IssueFilter>(r#"{"milestone":"v1"}"#)
|
|
.unwrap()
|
|
.search_text,
|
|
""
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn primary_destinations_are_ordered_unique_and_limited() {
|
|
let migrated: Preferences = serde_json::from_str("{}").unwrap();
|
|
assert_eq!(
|
|
migrated.primary_destinations,
|
|
default_primary_destinations()
|
|
);
|
|
|
|
let mut destinations = vec![
|
|
crate::PrimaryDestination::Actions,
|
|
crate::PrimaryDestination::Actions,
|
|
crate::PrimaryDestination::Issues,
|
|
crate::PrimaryDestination::Repositories,
|
|
crate::PrimaryDestination::PullRequests,
|
|
crate::PrimaryDestination::Milestones,
|
|
];
|
|
assert!(normalize_primary_destinations(&mut destinations));
|
|
assert_eq!(
|
|
destinations,
|
|
[
|
|
crate::PrimaryDestination::Actions,
|
|
crate::PrimaryDestination::Issues,
|
|
crate::PrimaryDestination::Repositories,
|
|
crate::PrimaryDestination::PullRequests,
|
|
]
|
|
);
|
|
}
|
|
}
|