feat: color category-saved channel names
This commit is contained in:
+106
-4
@@ -111,7 +111,7 @@ use ratatui::{
|
||||
layout::{Constraint, Layout, Rect},
|
||||
style::{Color, Style, Stylize},
|
||||
text::{Line, Span, Text},
|
||||
widgets::{Block, Clear, List, ListState, Paragraph, Row, Table, TableState, Wrap},
|
||||
widgets::{Block, Cell, Clear, List, ListState, Paragraph, Row, Table, TableState, Wrap},
|
||||
};
|
||||
use youtube::{Job, PAGE_SIZE, Page, PlayerLaunch, Video};
|
||||
|
||||
@@ -1677,10 +1677,24 @@ impl App {
|
||||
let view = &mut self.views[self.view_index];
|
||||
let table = Table::new(
|
||||
view.videos.iter().map(|video| {
|
||||
let tagged = self.config.categories.values().flatten().any(|identity| {
|
||||
video.channel_id.as_deref().is_some_and(|id| {
|
||||
identity.strip_prefix("https://www.youtube.com/channel/") == Some(id)
|
||||
}) || video.uploader_id.as_deref().is_some_and(|handle| {
|
||||
identity.strip_prefix("https://www.youtube.com/") == Some(handle)
|
||||
|| (video.channel_id.is_none()
|
||||
&& self.config.channels.get(handle) == Some(identity))
|
||||
})
|
||||
});
|
||||
let channel = Cell::from(video.channel.as_deref().unwrap_or("Unknown"));
|
||||
Row::new([
|
||||
video.title.as_str(),
|
||||
video.channel.as_deref().unwrap_or("Unknown"),
|
||||
video.publication_date.as_deref().unwrap_or("Unknown"),
|
||||
Cell::from(video.title.as_str()),
|
||||
if tagged {
|
||||
channel.fg(Color::Cyan)
|
||||
} else {
|
||||
channel
|
||||
},
|
||||
Cell::from(video.publication_date.as_deref().unwrap_or("Unknown")),
|
||||
])
|
||||
}),
|
||||
[
|
||||
@@ -2210,6 +2224,94 @@ mod tests {
|
||||
app.key(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn video_channel_colors_follow_category_membership_and_preserve_selection_contrast() {
|
||||
use ratatui::{Terminal, backend::TestBackend};
|
||||
|
||||
let mut app = app(&["tubular"]);
|
||||
let tagged_id = "UC4QobU6STFB0P71PMvOGN5A";
|
||||
let registered_id = "UCaYhcUwRBNscFNUKTjgPFiA";
|
||||
app.config.channels.insert(
|
||||
"@mapped".into(),
|
||||
config::normalize_channel(tagged_id).unwrap(),
|
||||
);
|
||||
app.config.channels.insert(
|
||||
"@registered".into(),
|
||||
config::normalize_channel(registered_id).unwrap(),
|
||||
);
|
||||
app.config.categories.insert(
|
||||
"Favorites".into(),
|
||||
vec![
|
||||
config::normalize_channel(tagged_id).unwrap(),
|
||||
config::normalize_channel("@direct").unwrap(),
|
||||
],
|
||||
);
|
||||
let cases = [
|
||||
("TaggedID", Some(tagged_id), None, true),
|
||||
("TaggedHandle", None, Some("@direct"), true),
|
||||
("MappedHandle", None, Some("@mapped"), true),
|
||||
(
|
||||
"Registered",
|
||||
Some(registered_id),
|
||||
Some("@registered"),
|
||||
false,
|
||||
),
|
||||
("TaggedID", Some(registered_id), None, false),
|
||||
("Unknown", None, None, false),
|
||||
];
|
||||
app.views[0].videos = youtube::search_results(
|
||||
&serde_json::json!({
|
||||
"entries": cases.iter().map(|(channel, id, handle, _)| serde_json::json!({
|
||||
"id": "jNQXAC9IVRw", "title": "Video title", "channel": channel,
|
||||
"channel_id": id, "uploader_id": handle, "upload_date": "20261011"
|
||||
})).collect::<Vec<_>>()
|
||||
})
|
||||
.to_string(),
|
||||
)
|
||||
.unwrap()
|
||||
.videos;
|
||||
app.views[0].selection.select(None);
|
||||
let mut terminal = Terminal::new(TestBackend::new(120, 24)).unwrap();
|
||||
for cleared in [false, true] {
|
||||
if cleared {
|
||||
app.config.categories.clear();
|
||||
}
|
||||
terminal
|
||||
.draw(|frame| app.draw(frame, Duration::ZERO))
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
// One tab row, three search rows, then the table border and header.
|
||||
for (index, (label, _, _, tagged)) in cases.iter().enumerate() {
|
||||
let y = 6 + index as u16;
|
||||
let row: String = (0..120).map(|x| buffer[(x, y)].symbol()).collect();
|
||||
let x = row.find(label).expect("channel label is visible") as u16;
|
||||
assert_eq!(
|
||||
buffer[(x, y)].fg,
|
||||
if *tagged && !cleared {
|
||||
Color::Cyan
|
||||
} else {
|
||||
Color::Reset
|
||||
},
|
||||
"{label} in row {index}, cleared={cleared}"
|
||||
);
|
||||
assert_eq!(buffer[(3, y)].fg, Color::Reset, "title stays uncolored");
|
||||
assert_eq!(buffer[(108, y)].fg, Color::Reset, "date stays uncolored");
|
||||
}
|
||||
if !cleared {
|
||||
app.views[0].selection.select(Some(0));
|
||||
terminal
|
||||
.draw(|frame| app.draw(frame, Duration::ZERO))
|
||||
.unwrap();
|
||||
let buffer = terminal.backend().buffer();
|
||||
let row: String = (0..120).map(|x| buffer[(x, 6)].symbol()).collect();
|
||||
let x = row.find("TaggedID").unwrap() as u16;
|
||||
assert_eq!(buffer[(x, 6)].fg, Color::Black);
|
||||
assert_eq!(buffer[(x, 6)].bg, Color::Cyan);
|
||||
app.views[0].selection.select(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remapped_preview_and_input_actions_survive_key_only_config_reload() {
|
||||
use ratatui::{Terminal, backend::TestBackend};
|
||||
|
||||
Reference in New Issue
Block a user