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() {
58 "Close details"
59 } else {
60 "Activate"
61 },
62 ),
63 self.menu_item(ManagerAction::Space, self.space_verb()),
64 self.menu_item(ManagerAction::Back, COLLECTION),
65 ],
66 ManagerPage::Formats(id) => vec![
67 self.cursor_item(self.choice_verb(id)),
68 self.menu_item(ManagerAction::Activate, "Accept"),
69 self.menu_item(ManagerAction::Back, "Details"),
70 ],
71 ManagerPage::Log(_) | ManagerPage::Report(_) | ManagerPage::Command(_) => {
72 vec![self.menu_item(ManagerAction::Back, "Details")]
73 }
74 ManagerPage::AddSources => vec![
75 self.menu_item(ManagerAction::Previous, "Move"),
76 self.menu_item(ManagerAction::Next, "Move"),
77 self.menu_item(ManagerAction::Activate, "Add sources"),
78 self.menu_item(ManagerAction::Back, COLLECTION),
79 ],
80 ManagerPage::Output(_) | ManagerPage::Input(_) => vec![
81 self.menu_item(ManagerAction::Previous, "Move"),
82 self.menu_item(ManagerAction::Next, "Move"),
83 self.menu_item(ManagerAction::Activate, "Save"),
84 self.menu_item(ManagerAction::Back, "Details"),
85 ],
86 };
87 items
88 .into_iter()
89 .chain([self.menu_item(ManagerAction::Exit, "Quit")])
90 }
91
92 fn menu_item(&self, action: ManagerAction, verb: impl Into<String>) -> MenuItem {
94 MenuItem {
95 action,
96 keys: self.action_keys(action).take(1).collect(),
97 verb: verb.into(),
98 availability: self.action_availability(action),
99 }
100 }
101
102 fn cursor_item(&self, verb: impl Into<String>) -> MenuItem {
104 let keys = self
105 .action_keys(ManagerAction::Previous)
106 .take(1)
107 .chain(self.action_keys(ManagerAction::Next).take(1))
108 .collect();
109 MenuItem {
110 action: ManagerAction::Next,
111 keys,
112 verb: verb.into(),
113 availability: self.action_availability(ManagerAction::Next),
114 }
115 }
116
117 fn choice_verb(&self, id: This::Id) -> &'static str {
119 let chooses_media = self
120 .queue_entry(id)
121 .is_some_and(|entry| entry.performer() == Performer::Retrieval);
122 if chooses_media {
123 "Choose format"
124 } else {
125 "Choose transfer"
126 }
127 }
128
129 fn space_verb(&self) -> String {
131 let entry = self.selected_queue_id().and_then(|id| self.queue_entry(id));
132 let Some(entry) = entry else {
133 return UNSTATED.to_owned();
134 };
135 entry
136 .space_action()
137 .map_or_else(
138 || match entry.phase() {
139 TransferPhase::Extracting | TransferPhase::Downloading => "Pause",
140 TransferPhase::Paused => "Resume",
141 TransferPhase::Ready => "Start",
142 TransferPhase::Waiting
143 | TransferPhase::Rehearsing
144 | TransferPhase::Publishing
145 | TransferPhase::Complete
146 | TransferPhase::Failed => UNSTATED,
147 },
148 SpaceAction::space_label,
149 )
150 .to_owned()
151 }
152}