diff --git a/appa-cosmic/src/app.rs b/appa-cosmic/src/app.rs index 60eea23..d710a4a 100644 --- a/appa-cosmic/src/app.rs +++ b/appa-cosmic/src/app.rs @@ -1,13 +1,15 @@ -use std::{borrow::Cow, cmp::Ordering, fmt, path::PathBuf}; +use std::{borrow::Cow, cmp::Ordering, fmt, path::PathBuf, sync::LazyLock}; use appa::core::{Dashboard, FolderDetails, FolderSummary}; use cosmic::{ iced::{Alignment, Length}, prelude::*, - widget::{self, table}, + widget::{self, menu, table}, }; const APP_ID: &str = "io.github.appa.Appa"; +static MENU_ID: LazyLock = + LazyLock::new(|| cosmic::iced::id::Id::new("appa-menu")); pub struct AppModel { core: cosmic::Core, @@ -24,6 +26,7 @@ pub struct AppModel { #[derive(Clone, Debug)] pub enum Message { + Surface(cosmic::surface::Action), DashboardLoaded(Result), FolderDetailsLoaded(Result), FolderPathChanged(String), @@ -31,6 +34,7 @@ pub enum Message { Refresh, FolderSelected(table::Entity), SortFolders(FolderColumn), + OpenSelectedFolder, ShowAddFolder, ShowJoinFolder, CloseForm, @@ -54,6 +58,31 @@ enum FolderForm { Join, } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum MenuAction { + AddFolder, + JoinFolder, + OpenSelectedFolder, + Refresh, + CloseDetails, + Sort(FolderColumn), +} + +impl menu::Action for MenuAction { + type Message = Message; + + fn message(&self) -> Self::Message { + match self { + Self::AddFolder => Message::ShowAddFolder, + Self::JoinFolder => Message::ShowJoinFolder, + Self::OpenSelectedFolder => Message::OpenSelectedFolder, + Self::Refresh => Message::Refresh, + Self::CloseDetails => Message::CloseFolderDetails, + Self::Sort(column) => Message::SortFolders(*column), + } + } +} + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum FolderColumn { #[default] @@ -67,7 +96,7 @@ impl fmt::Display for FolderColumn { fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(match self { Self::Name => "Name", - Self::Synced => "Synced", + Self::Synced => "Last synced", Self::Devices => "Devices", Self::Files => "Files", }) @@ -78,7 +107,7 @@ impl table::ItemCategory for FolderColumn { fn width(&self) -> Length { match self { Self::Name => Length::Fill, - Self::Synced => Length::Fixed(200.0), + Self::Synced => Length::Fixed(180.0), Self::Devices => Length::Fixed(140.0), Self::Files => Length::Fixed(100.0), } @@ -173,6 +202,11 @@ impl cosmic::Application for AppModel { fn update(&mut self, message: Message) -> Task> { match message { + Message::Surface(action) => { + return cosmic::task::message(cosmic::Action::Cosmic( + cosmic::app::Action::Surface(action), + )); + } Message::DashboardLoaded(result) => match result { Ok(dashboard) => { update_folder_table(&mut self.folder_table, &dashboard.folders); @@ -196,6 +230,13 @@ impl cosmic::Application for AppModel { } } Message::SortFolders(column) => sort_folder_table(&mut self.folder_table, column), + Message::OpenSelectedFolder => { + if let Some(folder) = self.folder_table.item(self.folder_table.active()) { + if let Err(error) = open::that_detached(&folder.path) { + self.notice = Some(format!("Could not open the folder: {error}")); + } + } + } Message::ShowAddFolder => self.active_form = Some(FolderForm::Add), Message::ShowJoinFolder => self.active_form = Some(FolderForm::Join), Message::CloseForm => self.active_form = None, @@ -237,25 +278,65 @@ impl cosmic::Application for AppModel { Task::none() } - fn header_end(&self) -> Vec> { - vec![ - widget::button::text("Add") - .on_press(Message::ShowAddFolder) - .into(), - widget::button::text("Join") - .on_press(Message::ShowJoinFolder) - .into(), - widget::button::text("Refresh") - .on_press(Message::Refresh) - .into(), - ] + fn header_start(&self) -> Vec> { + let key_bindings = std::collections::HashMap::new(); + vec![widget::responsive_menu_bar().into_element( + self.core(), + &key_bindings, + MENU_ID.clone(), + Message::Surface, + vec![ + ( + "File", + vec![ + menu::Item::Button("Add folder", None, MenuAction::AddFolder), + menu::Item::Button("Join folder", None, MenuAction::JoinFolder), + menu::Item::Divider, + menu::Item::Button( + "Open selected folder", + None, + MenuAction::OpenSelectedFolder, + ), + ], + ), + ( + "Edit", + vec![menu::Item::Button("Refresh", None, MenuAction::Refresh)], + ), + ( + "View", + vec![menu::Item::Button( + "Hide details", + None, + MenuAction::CloseDetails, + )], + ), + ( + "Sort", + vec![ + menu::Item::Button("By name", None, MenuAction::Sort(FolderColumn::Name)), + menu::Item::Button( + "By last synced", + None, + MenuAction::Sort(FolderColumn::Synced), + ), + menu::Item::Button( + "By devices", + None, + MenuAction::Sort(FolderColumn::Devices), + ), + menu::Item::Button("By files", None, MenuAction::Sort(FolderColumn::Files)), + ], + ), + ], + )] } } impl AppModel { fn folders_view(&self) -> Element<'_, Message> { let mut content = widget::column::with_capacity(4) - .push(widget::text::title1("Folders")) + .push(widget::text::title1("Shared folders")) .spacing(cosmic::theme::spacing().space_s); if let Some(notice) = &self.notice { @@ -410,7 +491,7 @@ fn folder_health(folder: &FolderSummary) -> String { return format!("{} conflicts need review", folder.conflict_count); } match folder.last_successful_sync { - Some(sync_time) => format!("Synced {sync_time}"), + Some(sync_time) => format!("Synced {}", sync_time.date()), None => "Waiting for the first sync".to_owned(), } }