feat: show horizontal scroll title
Show a horizontal scroll title, with arrows, if available
This commit is contained in:
@@ -201,6 +201,7 @@ impl<T> StatefulList<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the current status of the select list, e.g. 2/5,
|
/// Return the current status of the select list, e.g. 2/5,
|
||||||
|
/// MAYBE add up down arrows, check if at start or end etc
|
||||||
pub fn get_state_title(&self) -> String {
|
pub fn get_state_title(&self) -> String {
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
@@ -597,13 +598,12 @@ impl LogsTz {
|
|||||||
/// stateful list dependent on whether the timestamp is in the HashSet or not
|
/// stateful list dependent on whether the timestamp is in the HashSet or not
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct Logs {
|
pub struct Logs {
|
||||||
// should just be list of spans?
|
|
||||||
lines: StatefulList<Text<'static>>,
|
lines: StatefulList<Text<'static>>,
|
||||||
tz: HashSet<LogsTz>,
|
tz: HashSet<LogsTz>,
|
||||||
// could probably be a u16
|
|
||||||
offset: u16,
|
offset: u16,
|
||||||
max_log_len: usize,
|
max_log_len: usize,
|
||||||
adjusted_max_width: usize,
|
adjusted_max_width: usize,
|
||||||
|
adjust_max_width_text_len: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Logs {
|
impl Default for Logs {
|
||||||
@@ -615,6 +615,7 @@ impl Default for Logs {
|
|||||||
tz: HashSet::new(),
|
tz: HashSet::new(),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
adjusted_max_width: 0,
|
adjusted_max_width: 0,
|
||||||
|
adjust_max_width_text_len: 0,
|
||||||
max_log_len: 0,
|
max_log_len: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -629,10 +630,24 @@ impl Logs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO test me!
|
||||||
/// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y`
|
/// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y`
|
||||||
pub fn get_scroll_title(&self) -> Option<String> {
|
pub fn get_scroll_title(&mut self, width: u16) -> Option<String> {
|
||||||
if self.offset > 0 {
|
if self.horizontal_scroll_able(width) {
|
||||||
Some(format!(" {}/{} ", self.offset, self.adjusted_max_width))
|
let text_width = self.adjust_max_width_text_len;
|
||||||
|
let arrow_left = if self.offset > 0 { " ←" } else { " " };
|
||||||
|
let arrow_right = if usize::from(self.offset) < self.adjusted_max_width {
|
||||||
|
"→ "
|
||||||
|
} else {
|
||||||
|
" "
|
||||||
|
};
|
||||||
|
Some(format!(
|
||||||
|
"{left} {offset:>text_width$}/{adjusted_max_width} {right}",
|
||||||
|
offset = self.offset,
|
||||||
|
adjusted_max_width = self.adjusted_max_width,
|
||||||
|
left = arrow_left,
|
||||||
|
right = arrow_right,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -709,13 +724,23 @@ impl Logs {
|
|||||||
self.lines.get_state_title()
|
self.lines.get_state_title()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return true it currently selected cotnainer logs are wide enough to horizontally scroll
|
||||||
|
pub fn horizontal_scroll_able(&mut self, width: u16) -> bool {
|
||||||
|
if self.lines.items.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self.adjusted_max_width = self.max_log_len.saturating_sub(width.into()) + 4;
|
||||||
|
self.adjust_max_width_text_len = self.adjusted_max_width.to_string().chars().count();
|
||||||
|
self.max_log_len + 4 > usize::from(width)
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a padding so one char will always be visilbe?
|
/// Add a padding so one char will always be visilbe?
|
||||||
/// +6 is to account for borders & the selection triangle and a little bit of padding
|
|
||||||
pub fn forward(&mut self, width: u16) {
|
pub fn forward(&mut self, width: u16) {
|
||||||
let offset = usize::from(self.offset);
|
let offset = usize::from(self.offset);
|
||||||
self.adjusted_max_width = self.max_log_len.saturating_sub(width.into()) + 6;
|
if self.horizontal_scroll_able(width) {
|
||||||
if self.adjusted_max_width > 0 && offset < self.adjusted_max_width {
|
if self.adjusted_max_width > 0 && offset < self.adjusted_max_width {
|
||||||
self.offset = self.offset.saturating_add(1);
|
self.offset = self.offset.saturating_add(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,7 +938,7 @@ mod tests {
|
|||||||
text::{Line, Text},
|
text::{Line, Text},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app_data::{ContainerImage, Logs, LogsTz, RunningState},
|
app_data::{ContainerImage, Logs, LogsTz, RunningState},
|
||||||
ui::log_sanitizer,
|
ui::log_sanitizer,
|
||||||
};
|
};
|
||||||
@@ -1153,4 +1178,38 @@ mod tests {
|
|||||||
result
|
result
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
/// Test the get_scroll_title methods
|
||||||
|
fn test_scroll_title() {
|
||||||
|
let mut logs = Logs::default();
|
||||||
|
|
||||||
|
let result = logs.get_scroll_title(10);
|
||||||
|
assert!(result.is_none());
|
||||||
|
|
||||||
|
let input = "short".to_owned();
|
||||||
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
|
logs.insert(Text::from(input), tz);
|
||||||
|
|
||||||
|
let result = logs.get_scroll_title(10);
|
||||||
|
assert!(result.is_none());
|
||||||
|
|
||||||
|
let input = "2023-01-14T19:13:30.783138328Z Hello world some long line".to_owned();
|
||||||
|
let (tz, _) = LogsTz::splitter(&input);
|
||||||
|
logs.insert(Text::from(input), tz);
|
||||||
|
|
||||||
|
let result = logs.get_scroll_title(10);
|
||||||
|
assert_eq!(result, Some(" 0/51 → ".to_owned()));
|
||||||
|
|
||||||
|
logs.forward(10);
|
||||||
|
|
||||||
|
let result = logs.get_scroll_title(10);
|
||||||
|
assert_eq!(result, Some(" ← 1/51 → ".to_owned()));
|
||||||
|
|
||||||
|
for _ in 0..=49 {
|
||||||
|
logs.forward(10);
|
||||||
|
}
|
||||||
|
let result = logs.get_scroll_title(10);
|
||||||
|
assert_eq!(result, Some(" ← 51/51 ".to_owned()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -638,9 +638,9 @@ impl AppData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y`
|
/// If scrolling horiztonally along the logs, display a counter of the position in the in the scroll, `x/y`
|
||||||
pub fn get_scroll_title(&self) -> Option<String> {
|
pub fn get_scroll_title(&mut self, width: u16) -> Option<String> {
|
||||||
self.get_selected_container()
|
self.get_mut_selected_container()
|
||||||
.and_then(|i| i.logs.get_scroll_title())
|
.and_then(|i| i.logs.get_scroll_title(width))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Increase the logs offset, basically moving an invisible cursor back
|
/// Increase the logs offset, basically moving an invisible cursor back
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ impl DockerData {
|
|||||||
(None, None)
|
(None, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO is hardcoded eth0 a good idea here? - Could use first() instead?
|
// TODO is hardcoded eth0 a good idea here?
|
||||||
let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| {
|
let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| {
|
||||||
i.get("eth0").map_or((0, 0), |x| {
|
i.get("eth0").map_or((0, 0), |x| {
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -356,7 +356,6 @@ mod tests {
|
|||||||
insert_logs(&setup);
|
insert_logs(&setup);
|
||||||
|
|
||||||
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
let fd = FrameData::from((&setup.app_data, &setup.gui_state));
|
||||||
|
|
||||||
setup
|
setup
|
||||||
.terminal
|
.terminal
|
||||||
.draw(|f| {
|
.draw(|f| {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ pub mod tests {
|
|||||||
/// Create a FrameData struct from two Arc<mutex>'s, instead of from UI
|
/// Create a FrameData struct from two Arc<mutex>'s, instead of from UI
|
||||||
impl From<(&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)> for FrameData {
|
impl From<(&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)> for FrameData {
|
||||||
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
fn from(data: (&Arc<Mutex<AppData>>, &Arc<Mutex<GuiState>>)) -> Self {
|
||||||
let (app_data, gui_data) = (data.0.lock(), data.1.lock());
|
let (mut app_data, gui_data) = (data.0.lock(), data.1.lock());
|
||||||
|
|
||||||
// let container_section_height = app_data.get_container_len();
|
// let container_section_height = app_data.get_container_len();
|
||||||
// let container_section_height = if container_section_height < 12 {
|
// let container_section_height = if container_section_height < 12 {
|
||||||
@@ -185,7 +185,7 @@ pub mod tests {
|
|||||||
loading_icon: gui_data.get_loading().to_string(),
|
loading_icon: gui_data.get_loading().to_string(),
|
||||||
log_height: gui_data.get_log_height(),
|
log_height: gui_data.get_log_height(),
|
||||||
log_title: app_data.get_log_title(),
|
log_title: app_data.get_log_title(),
|
||||||
scroll_title: app_data.get_scroll_title(),
|
scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()),
|
||||||
port_max_lens: app_data.get_longest_port(),
|
port_max_lens: app_data.get_longest_port(),
|
||||||
ports: app_data.get_selected_ports(),
|
ports: app_data.get_selected_ports(),
|
||||||
selected_panel: gui_data.get_selected_panel(),
|
selected_panel: gui_data.get_selected_panel(),
|
||||||
@@ -216,6 +216,7 @@ pub mod tests {
|
|||||||
let gui_state = Arc::new(Mutex::new(gui_state));
|
let gui_state = Arc::new(Mutex::new(gui_state));
|
||||||
let fd = FrameData::from((&app_data, &gui_state));
|
let fd = FrameData::from((&app_data, &gui_state));
|
||||||
let area = Rect::new(0, 0, w, h);
|
let area = Rect::new(0, 0, w, h);
|
||||||
|
gui_state.lock().set_screen_width(w);
|
||||||
TuiTestSetup {
|
TuiTestSetup {
|
||||||
app_data,
|
app_data,
|
||||||
gui_state,
|
gui_state,
|
||||||
|
|||||||
+2
-2
@@ -312,7 +312,7 @@ pub struct FrameData {
|
|||||||
|
|
||||||
impl From<&Ui> for FrameData {
|
impl From<&Ui> for FrameData {
|
||||||
fn from(ui: &Ui) -> Self {
|
fn from(ui: &Ui) -> Self {
|
||||||
let (app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock());
|
let (mut app_data, gui_data) = (ui.app_data.lock(), ui.gui_state.lock());
|
||||||
|
|
||||||
let (filter_by, filter_term) = app_data.get_filter();
|
let (filter_by, filter_term) = app_data.get_filter();
|
||||||
Self {
|
Self {
|
||||||
@@ -333,7 +333,7 @@ impl From<&Ui> for FrameData {
|
|||||||
log_title: app_data.get_log_title(),
|
log_title: app_data.get_log_title(),
|
||||||
port_max_lens: app_data.get_longest_port(),
|
port_max_lens: app_data.get_longest_port(),
|
||||||
ports: app_data.get_selected_ports(),
|
ports: app_data.get_selected_ports(),
|
||||||
scroll_title: app_data.get_scroll_title(),
|
scroll_title: app_data.get_scroll_title(gui_data.get_screen_width()),
|
||||||
selected_panel: gui_data.get_selected_panel(),
|
selected_panel: gui_data.get_selected_panel(),
|
||||||
sorted_by: app_data.get_sorted(),
|
sorted_by: app_data.get_sorted(),
|
||||||
status: gui_data.get_status(),
|
status: gui_data.get_status(),
|
||||||
|
|||||||
Reference in New Issue
Block a user