Scrolling through list added, splash screen modified.
This commit is contained in:
parent
6b8b4ef355
commit
3bd8f1c75d
78
Cargo.lock
generated
78
Cargo.lock
generated
@ -301,6 +301,37 @@ dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@ -405,6 +436,12 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
|
||||
[[package]]
|
||||
name = "font8x8"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@ -928,6 +965,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
@ -936,8 +982,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "itunesdb"
|
||||
version = "0.1.50"
|
||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#2c38d91cd89908b20a2b08da1b3202dacf5d1f9b"
|
||||
version = "0.1.51"
|
||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ed639f96d59b6777114551e8c3054f2a0ac9c7da"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"env_logger",
|
||||
@ -1047,9 +1093,11 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"soundcloud",
|
||||
"throbber-widgets-tui",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"tui-big-text",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1344,7 +1392,7 @@ dependencies = [
|
||||
"crossterm",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
@ -1893,6 +1941,16 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "throbber-widgets-tui"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d36b5738d666a2b4c91b7c24998a8588db724b3107258343ebf8824bf55b06d"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"ratatui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
@ -2094,6 +2152,18 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tui-big-text"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97cefa9f1425ab6146db2961241cec86845d11105b5dd6bb504294b0cdd21af"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"font8x8",
|
||||
"itertools 0.14.0",
|
||||
"ratatui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
@ -2112,7 +2182,7 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
@ -20,7 +20,9 @@ futures = "0.3"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.12", features = ["codec"] }
|
||||
soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||
itunesdb = { version = "0.1.50", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
||||
itunesdb = { version = "0.1.51", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
||||
puremp3 = "0.1.0"
|
||||
mp3-duration = "0.1.10"
|
||||
rand = "0.8.5"
|
||||
rand = "0.8.5"
|
||||
tui-big-text = "0.7.1"
|
||||
throbber-widgets-tui = "0.8.0"
|
||||
|
@ -4,7 +4,9 @@ pub mod table {
|
||||
use ratatui::widgets::{Block, Borders, Row, Table};
|
||||
use ratatui::Frame;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SmartTable {
|
||||
is_checked: bool,
|
||||
header: Vec<String>,
|
||||
data: Vec<Vec<String>>,
|
||||
constraints: Vec<Constraint>,
|
||||
@ -15,6 +17,7 @@ pub mod table {
|
||||
impl SmartTable {
|
||||
pub fn new(header: Vec<String>, constraints: Vec<Constraint>) -> Self {
|
||||
Self {
|
||||
is_checked: true,
|
||||
header,
|
||||
data: Vec::new(),
|
||||
constraints,
|
||||
@ -23,6 +26,10 @@ pub mod table {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_checked(&mut self, checked: bool) {
|
||||
self.is_checked = checked;
|
||||
}
|
||||
|
||||
pub fn set_data(&mut self, data: Vec<Vec<String>>) {
|
||||
self.data = data;
|
||||
}
|
||||
@ -49,14 +56,22 @@ pub mod table {
|
||||
for (i, entry) in self.data.iter().enumerate() {
|
||||
v.push(
|
||||
Row::new(entry.clone()).style(if self.selected_row as usize == i {
|
||||
Style::default().bg(Color::LightBlue).fg(Color::White)
|
||||
Style::default()
|
||||
.bg(if self.is_checked {
|
||||
Color::LightBlue
|
||||
} else {
|
||||
Color::Gray
|
||||
})
|
||||
.fg(Color::White)
|
||||
} else {
|
||||
Style::default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if self.selected_row as usize > area.rows().count() - 4 {
|
||||
if self.selected_row as usize > area.rows().count() - 4
|
||||
&& self.selected_row - ((area.rows().count() - 4) as i32) >= 0
|
||||
{
|
||||
v = v[(self.selected_row as usize - (area.rows().count() - 4))..].to_vec();
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ impl AppScreen for FileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut ratatui::Frame, theme: &Theme) {
|
||||
fn render(&self, frame: &mut Frame, _theme: &Theme) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
|
@ -9,7 +9,7 @@ use crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use loading_screen::LoadingScreen;
|
||||
use main_screen::MainScreen;
|
||||
use ratatui::{
|
||||
@ -18,6 +18,7 @@ use ratatui::{
|
||||
Frame, Terminal,
|
||||
};
|
||||
use screen::AppScreen;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, error::Error, io};
|
||||
use sync::AppEvent;
|
||||
use tokio::sync::mpsc::{self, Receiver, UnboundedSender};
|
||||
@ -134,6 +135,9 @@ impl App {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ = tokio::time::sleep(Duration::from_millis(200)) => {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,22 +4,21 @@ use ratatui::{
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style, Stylize},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Row, Table, Tabs},
|
||||
widgets::{Block, Borders, Paragraph, Tabs},
|
||||
Frame,
|
||||
};
|
||||
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::component::table::SmartTable;
|
||||
use crate::sync::DBPlaylist;
|
||||
use crate::{screen::AppScreen, sync::AppEvent, theme::Theme, AppState};
|
||||
|
||||
pub struct MainScreen {
|
||||
mode: bool,
|
||||
selected_tab: i8,
|
||||
selected_playlist: i32,
|
||||
selected_song: i32,
|
||||
max_pls: i32,
|
||||
max_songs: i32,
|
||||
pl_table: SmartTable,
|
||||
song_table: SmartTable,
|
||||
tab_titles: Vec<String>,
|
||||
soundcloud: Option<Vec<CloudPlaylist>>,
|
||||
playlists: Option<Vec<DBPlaylist>>,
|
||||
@ -33,17 +32,18 @@ impl AppScreen for MainScreen {
|
||||
KeyCode::Left => self.previous_tab(),
|
||||
KeyCode::Up => self.previous_row(),
|
||||
KeyCode::Down => self.next_row(),
|
||||
KeyCode::F(6) => self.download_row(),
|
||||
KeyCode::F(5) => self.download_row(),
|
||||
KeyCode::Tab => self.switch_mode(),
|
||||
KeyCode::F(2) => {
|
||||
self.sender
|
||||
KeyCode::F(4) => {
|
||||
let _ = self
|
||||
.sender
|
||||
.send(AppEvent::SwitchScreen(AppState::FileSystem));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, frame: &mut Frame, theme: &Theme) {
|
||||
fn render(&self, frame: &mut Frame, _theme: &Theme) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
@ -76,9 +76,11 @@ impl AppScreen for MainScreen {
|
||||
let status_bar = Paragraph::new(Line::from(vec![
|
||||
"◄ ► to change tab".bold(),
|
||||
" | ".dark_gray(),
|
||||
"<F5> SAVE FS".bold(),
|
||||
"<TAB> SWITCH PANEL".bold(),
|
||||
" | ".dark_gray(),
|
||||
"<F6> DL".bold(),
|
||||
"<F4> FS MODE".bold(),
|
||||
" | ".dark_gray(),
|
||||
"<F5> DOWNLOAD".bold(),
|
||||
" | ".dark_gray(),
|
||||
"<F8> DEL".bold(),
|
||||
" | ".dark_gray(),
|
||||
@ -97,10 +99,8 @@ impl MainScreen {
|
||||
pub fn new(sender: UnboundedSender<AppEvent>) -> Self {
|
||||
MainScreen {
|
||||
mode: false,
|
||||
selected_playlist: 0,
|
||||
selected_song: 0,
|
||||
max_pls: 0,
|
||||
max_songs: 0,
|
||||
pl_table: SmartTable::default(),
|
||||
song_table: SmartTable::default(),
|
||||
soundcloud: None,
|
||||
playlists: None,
|
||||
selected_tab: 0,
|
||||
@ -114,42 +114,14 @@ impl MainScreen {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_max_rows(&mut self) {
|
||||
self.selected_song = 0;
|
||||
self.selected_playlist = 0;
|
||||
self.max_songs = 0;
|
||||
self.max_pls = match self.selected_tab {
|
||||
1 => self.soundcloud.as_deref().unwrap_or(&[]).len(),
|
||||
2 => self.playlists.as_deref().unwrap_or(&[]).len(),
|
||||
_ => 0,
|
||||
}
|
||||
.try_into()
|
||||
.unwrap();
|
||||
self.update_max_songs();
|
||||
}
|
||||
|
||||
fn update_max_songs(&mut self) {
|
||||
if self.max_pls > 0 {
|
||||
self.max_songs = match self.selected_tab {
|
||||
1 => self
|
||||
.soundcloud
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.get(self.selected_playlist as usize)
|
||||
.unwrap()
|
||||
.tracks
|
||||
.len(),
|
||||
_ => 0,
|
||||
}
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
self.selected_song = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_mode(&mut self) {
|
||||
self.mode = !self.mode;
|
||||
self.set_mode(!self.mode);
|
||||
}
|
||||
|
||||
fn set_mode(&mut self, mode: bool) {
|
||||
self.mode = mode;
|
||||
self.pl_table.set_checked(!self.mode);
|
||||
self.song_table.set_checked(self.mode);
|
||||
}
|
||||
|
||||
fn next_tab(&mut self) {
|
||||
@ -157,31 +129,30 @@ impl MainScreen {
|
||||
self.selected_tab + 1,
|
||||
(self.tab_titles.len() - 1).try_into().unwrap(),
|
||||
);
|
||||
self.update_max_rows();
|
||||
self.update_tables();
|
||||
}
|
||||
|
||||
fn previous_tab(&mut self) {
|
||||
self.selected_tab = std::cmp::max(0, self.selected_tab - 1);
|
||||
self.update_max_rows();
|
||||
self.update_tables();
|
||||
}
|
||||
|
||||
fn previous_row(&mut self) {
|
||||
match self.mode {
|
||||
true => self.selected_song = std::cmp::max(0, self.selected_song - 1),
|
||||
true => self.song_table.previous_row(),
|
||||
false => {
|
||||
self.selected_playlist = std::cmp::max(0, self.selected_playlist - 1);
|
||||
self.update_max_songs();
|
||||
self.pl_table.previous_row();
|
||||
self.update_songs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
match self.mode {
|
||||
true => self.selected_song = std::cmp::min(self.selected_song + 1, self.max_songs - 1),
|
||||
true => self.song_table.next_row(),
|
||||
false => {
|
||||
self.selected_playlist =
|
||||
std::cmp::min(self.selected_playlist + 1, self.max_pls - 1);
|
||||
self.update_max_songs();
|
||||
self.pl_table.next_row();
|
||||
self.update_songs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,7 +164,7 @@ impl MainScreen {
|
||||
.soundcloud
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.get(self.selected_playlist as usize)
|
||||
.get(self.pl_table.selected_row())
|
||||
.unwrap()
|
||||
.clone();
|
||||
let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist));
|
||||
@ -203,72 +174,165 @@ impl MainScreen {
|
||||
pub fn set_soundcloud_playlists(&mut self, pl: CloudPlaylists) {
|
||||
self.soundcloud = Some(pl.collection);
|
||||
if self.selected_tab == 1 {
|
||||
self.update_max_rows();
|
||||
self.update_tables();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_itunes(&mut self, pl: Vec<DBPlaylist>) {
|
||||
self.playlists = Some(pl);
|
||||
if self.selected_tab == 2 {
|
||||
self.update_max_rows();
|
||||
self.update_tables();
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tables(&mut self) {
|
||||
self.set_mode(false);
|
||||
|
||||
self.pl_table = SmartTable::new(
|
||||
["Id", "Title", "Songs Count", "Date", "IS"]
|
||||
.iter_mut()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
[
|
||||
Constraint::Length(3), // ID column
|
||||
Constraint::Percentage(50), // Playlist name column
|
||||
Constraint::Percentage(20), // Song count column
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Length(2),
|
||||
]
|
||||
.to_vec(),
|
||||
);
|
||||
|
||||
let data = match self.selected_tab {
|
||||
1 => {
|
||||
if let Some(sc) = &self.soundcloud {
|
||||
sc.iter()
|
||||
.map(|playlist| {
|
||||
let date: DateTime<Utc> = playlist.created_at.parse().unwrap();
|
||||
vec![
|
||||
playlist.id.to_string(),
|
||||
playlist.title.clone(),
|
||||
[playlist.track_count.to_string(), " songs".to_string()].concat(),
|
||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||
"NO".to_string(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<Vec<String>>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
if let Some(it) = &self.playlists {
|
||||
it.iter()
|
||||
.map(|playlist| {
|
||||
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
|
||||
vec![
|
||||
playlist.id.to_string(),
|
||||
"".to_string(),
|
||||
playlist.tracks.len().to_string(),
|
||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||
"YES".to_string(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<Vec<String>>>()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.pl_table = SmartTable::default();
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
self.pl_table.set_data(data);
|
||||
self.pl_table.set_title("Playlists".to_string());
|
||||
self.update_songs();
|
||||
}
|
||||
|
||||
fn update_songs(&mut self) {
|
||||
let constraints = [
|
||||
Constraint::Length(3), // ID column
|
||||
Constraint::Percentage(50), // Playlist name column
|
||||
Constraint::Percentage(20), // Song count column
|
||||
Constraint::Length(5),
|
||||
Constraint::Min(0),
|
||||
]
|
||||
.to_vec();
|
||||
|
||||
match self.selected_tab {
|
||||
1 => {
|
||||
self.song_table = SmartTable::new(
|
||||
["Id", "Title", "Artist", "Duration", "Genre"]
|
||||
.iter_mut()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
constraints,
|
||||
);
|
||||
self.set_mode(self.mode);
|
||||
|
||||
if let Some(pls) = &self.soundcloud {
|
||||
let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks;
|
||||
let data = s
|
||||
.iter()
|
||||
.map(|track| {
|
||||
vec![
|
||||
track.id.to_string(),
|
||||
track.title.as_deref().unwrap().to_string(),
|
||||
track
|
||||
.user
|
||||
.clone()
|
||||
.unwrap()
|
||||
.username
|
||||
.unwrap_or(track.user.as_ref().unwrap().permalink.clone()),
|
||||
track.duration.unwrap_or(0).to_string(),
|
||||
track.genre.as_ref().unwrap_or(&String::new()).to_string(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<Vec<String>>>();
|
||||
|
||||
self.song_table.set_data(data);
|
||||
}
|
||||
self.song_table.set_title(" Songs ".to_string());
|
||||
}
|
||||
2 => {
|
||||
self.song_table = SmartTable::new(
|
||||
["Id", "Title", "Artist", "Bitrate", "Genre"]
|
||||
.iter_mut()
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
constraints,
|
||||
);
|
||||
self.set_mode(self.mode);
|
||||
|
||||
if let Some(pls) = &self.playlists {
|
||||
let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks;
|
||||
let data = s
|
||||
.iter()
|
||||
.map(|track| {
|
||||
vec![
|
||||
track.data.unique_id.to_string(),
|
||||
track.get_title(),
|
||||
track.get_location(),
|
||||
track.data.bitrate.to_string(),
|
||||
track.get_genre(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<Vec<String>>>();
|
||||
|
||||
self.song_table.set_data(data);
|
||||
}
|
||||
|
||||
self.song_table.set_title(" Songs ".to_string());
|
||||
}
|
||||
_ => {
|
||||
self.song_table = SmartTable::default();
|
||||
self.set_mode(self.mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_tab(&self, frame: &mut Frame, area: Rect) {
|
||||
let rows = match self.selected_tab {
|
||||
1 => {
|
||||
// SC
|
||||
let mut v = Vec::new();
|
||||
v.push(
|
||||
Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"])
|
||||
.style(Style::default().fg(Color::Gray)),
|
||||
);
|
||||
if let Some(s) = &self.soundcloud {
|
||||
for (i, playlist) in s.iter().enumerate() {
|
||||
let date: DateTime<Utc> = playlist.created_at.parse().unwrap();
|
||||
let mut row = Row::new(vec![
|
||||
playlist.id.to_string(),
|
||||
playlist.title.clone(),
|
||||
[playlist.track_count.to_string(), " songs".to_string()].concat(),
|
||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||
"NO".to_string(),
|
||||
]);
|
||||
if self.selected_playlist == i as i32 {
|
||||
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
||||
}
|
||||
v.push(row);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
2 => {
|
||||
// local
|
||||
let mut v = Vec::new();
|
||||
v.push(
|
||||
Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"])
|
||||
.style(Style::default().fg(Color::Gray)),
|
||||
);
|
||||
if let Some(s) = &self.playlists {
|
||||
for (i, playlist) in s.iter().enumerate() {
|
||||
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
|
||||
let mut row = Row::new(vec![
|
||||
playlist.id.to_string(),
|
||||
"".to_string(),
|
||||
playlist.tracks.len().to_string(),
|
||||
format!("{}", date.format("%Y-%m-%d %H:%M")),
|
||||
"YES".to_string(),
|
||||
]);
|
||||
if self.selected_playlist == i as i32 {
|
||||
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
||||
}
|
||||
v.push(row);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
@ -277,99 +341,7 @@ impl MainScreen {
|
||||
])
|
||||
.split(area);
|
||||
|
||||
// Create the table
|
||||
let table = Table::new(
|
||||
rows,
|
||||
&[
|
||||
Constraint::Length(3), // ID column
|
||||
Constraint::Percentage(50), // Playlist name column
|
||||
Constraint::Percentage(20), // Song count column
|
||||
Constraint::Percentage(30),
|
||||
Constraint::Length(2),
|
||||
],
|
||||
)
|
||||
.block(Block::default().borders(Borders::ALL).title(" Playlists "))
|
||||
.style(Style::default().fg(Color::Black));
|
||||
|
||||
frame.render_widget(table, chunks[0]);
|
||||
|
||||
let mut rows = match self.selected_tab {
|
||||
1 => {
|
||||
// sc
|
||||
let mut v = Vec::new();
|
||||
v.push(
|
||||
Row::new(vec!["Id", "Title", "Artist", "Duration", "Genre"])
|
||||
.style(Style::default().fg(Color::Gray)),
|
||||
);
|
||||
if let Some(pls) = &self.soundcloud {
|
||||
let s = &pls.get(self.selected_playlist as usize).unwrap().tracks;
|
||||
for (i, track) in s.iter().enumerate() {
|
||||
let mut row = Row::new(vec![
|
||||
track.id.to_string(),
|
||||
track.title.as_deref().unwrap().to_string(),
|
||||
track
|
||||
.user
|
||||
.clone()
|
||||
.unwrap()
|
||||
.username
|
||||
.unwrap_or(track.user.as_ref().unwrap().permalink.clone()),
|
||||
track.duration.unwrap_or(0).to_string(),
|
||||
track.genre.as_ref().unwrap_or(&String::new()).to_string(),
|
||||
]);
|
||||
if self.selected_song == i as i32 {
|
||||
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
||||
}
|
||||
v.push(row);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
2 => {
|
||||
// local
|
||||
let mut v = Vec::new();
|
||||
v.push(
|
||||
Row::new(vec!["Id", "Title", "Artist", "Bitrate", "Genre"])
|
||||
.style(Style::default().fg(Color::Gray)),
|
||||
);
|
||||
if let Some(pls) = &self.playlists {
|
||||
let s = &pls.get(self.selected_playlist as usize).unwrap().tracks;
|
||||
for (i, track) in s.iter().enumerate() {
|
||||
let mut row = Row::new(vec![
|
||||
track.data.unique_id.to_string(),
|
||||
track.get_title(),
|
||||
track.get_location(),
|
||||
track.data.bitrate.to_string(),
|
||||
track.get_genre(),
|
||||
]);
|
||||
if self.selected_song == i as i32 {
|
||||
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
|
||||
}
|
||||
v.push(row);
|
||||
}
|
||||
}
|
||||
v
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
|
||||
if chunks[1].rows().count() <= self.selected_song as usize {
|
||||
rows = rows[self.selected_song as usize..].to_vec();
|
||||
}
|
||||
|
||||
// Create the table
|
||||
let table = Table::new(
|
||||
rows,
|
||||
&[
|
||||
Constraint::Length(3), // ID column
|
||||
Constraint::Percentage(50), // Playlist name column
|
||||
Constraint::Percentage(20), // Song count column
|
||||
Constraint::Length(5),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
)
|
||||
.block(Block::default().borders(Borders::ALL).title(" Songs "))
|
||||
.style(Style::default().fg(Color::Black));
|
||||
|
||||
frame.render_widget(table, chunks[1]);
|
||||
self.pl_table.render(frame, chunks[0]);
|
||||
self.song_table.render(frame, chunks[1]);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,14 @@
|
||||
use crate::{screen::AppScreen, theme::Theme};
|
||||
use color_eyre::owo_colors::OwoColorize;
|
||||
use ratatui::layout::{Constraint, Direction, Flex, Layout};
|
||||
use ratatui::widgets::Paragraph;
|
||||
use ratatui::{
|
||||
style::{Style, Stylize},
|
||||
symbols::border,
|
||||
text::{Line, Text},
|
||||
widgets::{Block, Paragraph},
|
||||
text::Line,
|
||||
Frame,
|
||||
};
|
||||
|
||||
use crate::{screen::AppScreen, theme::Theme};
|
||||
use throbber_widgets_tui::{ThrobberState, BOX_DRAWING};
|
||||
use tui_big_text::{BigText, PixelSize};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct WaitScreen {}
|
||||
@ -16,21 +17,37 @@ impl AppScreen for WaitScreen {
|
||||
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
|
||||
|
||||
fn render(&self, frame: &mut Frame, theme: &Theme) {
|
||||
let title = Line::from(" Lyrica ".bold());
|
||||
let instructions = Line::from(vec![" Quit ".into(), "<Q> ".red().bold()]);
|
||||
let block = Block::bordered()
|
||||
.title(title.centered())
|
||||
.title_bottom(instructions.centered())
|
||||
.border_set(border::ROUNDED);
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(33); 3])
|
||||
.split(frame.area());
|
||||
|
||||
let counter_text = Text::from(vec![Line::from(vec!["Searching for iPod...".into()])]);
|
||||
let simple = throbber_widgets_tui::Throbber::default()
|
||||
.label("Searching for your iPod")
|
||||
.throbber_set(BOX_DRAWING);
|
||||
|
||||
let par = Paragraph::new(counter_text)
|
||||
.style(Style::new().bg(theme.background()))
|
||||
let bottom =
|
||||
Paragraph::new(vec![Line::from(vec![" Quit ".into(), "<Q> ".red().bold()])]).centered();
|
||||
let bottom_l =
|
||||
Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).split(chunks[2]);
|
||||
|
||||
let [throbber_l] = Layout::horizontal([Constraint::Length(
|
||||
simple.to_line(&ThrobberState::default()).width() as u16,
|
||||
)])
|
||||
.flex(Flex::Center)
|
||||
.areas(bottom_l[0]);
|
||||
|
||||
frame.render_widget(simple, throbber_l);
|
||||
frame.render_widget(bottom, bottom_l[1]);
|
||||
|
||||
let title = BigText::builder()
|
||||
.pixel_size(PixelSize::Full)
|
||||
.style(Style::new().blue())
|
||||
.lines(vec!["Lyrica".light_blue().into()])
|
||||
.centered()
|
||||
.block(block);
|
||||
.build();
|
||||
|
||||
frame.render_widget(par, frame.area());
|
||||
frame.render_widget(title, chunks[1]);
|
||||
}
|
||||
|
||||
fn as_any(&mut self) -> &mut dyn std::any::Any {
|
||||
|
Loading…
x
Reference in New Issue
Block a user