first commit

This commit is contained in:
2026-04-11 14:11:11 -06:00
commit ed34c56e31
6 changed files with 2819 additions and 0 deletions

180
src/app.rs Normal file
View File

@@ -0,0 +1,180 @@
use serde::{Deserialize, Serialize};
// ─── Domain types ─────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Deserialize)]
pub struct Space {
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Page {
pub id: String,
pub title: Option<String>,
}
#[derive(Deserialize)]
pub struct DataWrapper<T> {
pub items: Vec<T>,
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum ApiList<T> {
Wrapped { data: DataWrapper<T> },
Paginated { items: Vec<T> },
Plain(Vec<T>),
}
impl<T> ApiList<T> {
pub fn into_items(self) -> Vec<T> {
match self {
ApiList::Wrapped { data } => data.items,
ApiList::Paginated { items } => items,
ApiList::Plain(v) => v,
}
}
}
// ─── Login form ───────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AppState {
Login,
Main,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LoginField {
Url,
Email,
Password,
}
impl LoginField {
pub fn next(self) -> Self {
match self {
LoginField::Url => LoginField::Email,
LoginField::Email => LoginField::Password,
LoginField::Password => LoginField::Url,
}
}
pub fn prev(self) -> Self {
match self {
LoginField::Url => LoginField::Password,
LoginField::Email => LoginField::Url,
LoginField::Password => LoginField::Email,
}
}
}
pub struct LoginForm {
pub url: String,
pub email: String,
pub password: String,
pub active_field: LoginField,
pub error: Option<String>,
pub submitting: bool,
}
impl LoginForm {
pub fn new() -> Self {
Self {
url: String::from("https://docmost.nakano47.com"),
email: String::from("chamagua1@proton.me"),
password: String::new(),
active_field: LoginField::Email,
error: None,
submitting: false,
}
}
pub fn active_field_value_mut(&mut self) -> &mut String {
match self.active_field {
LoginField::Url => &mut self.url,
LoginField::Email => &mut self.email,
LoginField::Password => &mut self.password,
}
}
}
// ─── Main view ────────────────────────────────────────────────────────────────
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Panel {
Spaces,
Pages,
}
pub struct MainView {
pub spaces: Vec<Space>,
pub pages: Vec<Page>,
pub selected_space: usize,
pub selected_page: usize,
pub focus: Panel,
pub loading_spaces: bool,
pub loading_pages: bool,
pub error: Option<String>,
}
impl MainView {
pub fn new() -> Self {
Self {
spaces: vec![],
pages: vec![],
selected_space: 0,
selected_page: 0,
focus: Panel::Spaces,
loading_spaces: true,
loading_pages: false,
error: None,
}
}
}
// ─── Root app ─────────────────────────────────────────────────────────────────
pub struct App {
pub state: AppState,
pub login: LoginForm,
pub main: MainView,
pub base_url: String,
pub token: String,
}
impl App {
pub fn new() -> Self {
Self {
state: AppState::Login,
login: LoginForm::new(),
main: MainView::new(),
base_url: String::new(),
token: String::new(),
}
}
}
// ─── Messages ─────────────────────────────────────────────────────────────────
pub enum AppMsg {
LoginSuccess { token: String, base_url: String },
LoginError(String),
SpacesLoaded(Vec<Space>),
PagesLoaded(Vec<Page>),
ApiError(String),
}
// ─── Request DTOs ─────────────────────────────────────────────────────────────
#[derive(Serialize)]
pub struct LoginRequest<'a> {
pub email: &'a str,
pub password: &'a str,
}
#[derive(Serialize)]
pub struct SidebarPagesRequest<'a> {
#[serde(rename = "spaceId")]
pub space_id: &'a str,
}