1use crate::*;
2use alux_ext::ext;
3use rsynko_manager::*;
4
5pub struct MenuItem {
7 pub action: ManagerAction,
9 pub keys: Vec<Keystroke>,
11 pub verb: String,
13 pub availability: ActionAvailability,
15}
16
17impl MenuItem {
18 #[must_use]
20 pub fn label(&self) -> String {
21 let keys = self.keys.iter().map(|key| key.label()).collect::<String>();
22 format!("[{keys}] {}", self.verb)
23 }
24}
25
26#[ext(name = MenuPresentationExt)]
28pub impl<This> This
29where
30 This: NavigationStateAlg
31 + QueueCatalogAlg
32 + DraftStateAlg
33 + OutputDraftAlg
34 + InputDraftAlg
35 + TextEditorStateAlg
36 + DetailSelectionAlg,
37 This::Entry: QueueEntryAlg + RequestOptionsAlg,
38 This::Id: Copy + Eq,
39{
40 fn menu_items(&self) -> impl Iterator<Item = MenuItem> {
44 let page = self.page();
45 let items = match page {
46 ManagerPage::Collection => vec![
47 self.cursor_item("Select"),
48 self.menu_item(ManagerAction::Activate, "Details"),
49 self.menu_item(ManagerAction::AddSources, "Add"),
50 self.menu_item(ManagerAction::Space, self.space_verb()),
51 self.menu_item(ManagerAction::Remove, "Remove"),
52 ],
53 ManagerPage::Details(_) => vec![
54 self.cursor_item("Select field or action"),
55 self.menu_item(
56 ManagerAction::Activate,
57 if self.selected_detail_control().is_none() { "Close details" } else { "Activate" },
58 ),
59 self.menu_item(ManagerAction::Space, self.space_verb()),
60 self.menu_item(ManagerAction::Back, COLLECTION),
61 ],
62 ManagerPage::Formats(id) => vec![
63 self.cursor_item(self.choice_verb(id)),
64 self.menu_item(ManagerAction::Activate, "Accept"),
65 self.menu_item(ManagerAction::Back, "Details"),
66 ],
67 ManagerPage::Log(_) | ManagerPage::Report(_) | ManagerPage::Command(_) => {
68 vec![self.menu_item(ManagerAction::Back, "Details")]
69 }
70 ManagerPage::AddSources => vec![
71 self.menu_item(ManagerAction::Previous, "Move"),
72 self.menu_item(ManagerAction::Next, "Move"),
73 self.menu_item(ManagerAction::Activate, "Add sources"),
74 self.menu_item(ManagerAction::Back, COLLECTION),
75 ],
76 ManagerPage::Output(_) | ManagerPage::Input(_) => vec![
77 self.menu_item(ManagerAction::Previous, "Move"),
78 self.menu_item(ManagerAction::Next, "Move"),
79 self.menu_item(ManagerAction::Activate, "Save"),
80 self.menu_item(ManagerAction::Back, "Details"),
81 ],
82 };
83 items.into_iter().chain([self.menu_item(ManagerAction::Exit, "Quit")])
84 }
85
86 fn menu_item(&self, action: ManagerAction, verb: impl Into<String>) -> MenuItem {
88 MenuItem {
89 action,
90 keys: self.action_keys(action).take(1).collect(),
91 verb: verb.into(),
92 availability: self.action_availability(action),
93 }
94 }
95
96 fn cursor_item(&self, verb: impl Into<String>) -> MenuItem {
98 let keys = self
99 .action_keys(ManagerAction::Previous)
100 .take(1)
101 .chain(self.action_keys(ManagerAction::Next).take(1))
102 .collect();
103 MenuItem {
104 action: ManagerAction::Next,
105 keys,
106 verb: verb.into(),
107 availability: self.action_availability(ManagerAction::Next),
108 }
109 }
110
111 fn choice_verb(&self, id: This::Id) -> &'static str {
113 let chooses_media = self.queue_entry(id).is_some_and(|entry| entry.performer() == Performer::Retrieval);
114 if chooses_media { "Choose format" } else { "Choose transfer" }
115 }
116
117 fn space_verb(&self) -> String {
119 let entry = self.selected_queue_id().and_then(|id| self.queue_entry(id));
120 let Some(entry) = entry else {
121 return UNSTATED.to_owned();
122 };
123 entry
124 .space_action()
125 .map_or_else(
126 || match entry.phase() {
127 TransferPhase::Extracting | TransferPhase::Downloading => "Pause",
128 TransferPhase::Paused => "Resume",
129 TransferPhase::Ready => "Start",
130 TransferPhase::Waiting
131 | TransferPhase::Rehearsing
132 | TransferPhase::Publishing
133 | TransferPhase::Complete
134 | TransferPhase::Failed => UNSTATED,
135 },
136 SpaceAction::space_label,
137 )
138 .to_owned()
139 }
140}