modified: Cargo.lock
modified: Cargo.toml modified: src/config.rs modified: src/main.rs new file: src/screen.rs new file: src/tabs.rs
This commit is contained in:
parent
41aa3222a8
commit
60e92ee1d7
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -827,8 +827,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "itunesdb"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#9e20fe785dc9cd1268641dad9730a8fb3ff246c5"
|
||||
version = "0.1.1"
|
||||
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#5a6ca7a9f5eca42959e3498a6eb2700f502b5509"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"env_logger",
|
||||
@ -932,6 +932,7 @@ dependencies = [
|
||||
"rusb",
|
||||
"serde",
|
||||
"soundcloud",
|
||||
"strum 0.27.0",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
@ -1204,7 +1205,7 @@ dependencies = [
|
||||
"itertools",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
"strum 0.26.3",
|
||||
"time",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
@ -1562,8 +1563,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "soundcloud"
|
||||
version = "0.1.0"
|
||||
source = "git+https://gitea.awain.net/alterwain/soundcloud_api.git#d4d51c64e9225763f6d40a7f450c673ab6e36ddf"
|
||||
version = "0.1.1"
|
||||
source = "git+https://gitea.awain.net/alterwain/soundcloud_api.git#87614c2a10c30f7d3e4b3cb0f8973d62ffec7916"
|
||||
dependencies = [
|
||||
"hyper-util",
|
||||
"regex",
|
||||
@ -1603,7 +1604,16 @@ version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
"strum_macros 0.26.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f"
|
||||
dependencies = [
|
||||
"strum_macros 0.27.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1619,6 +1629,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
@ -16,5 +16,6 @@ color-eyre = "0.6.3"
|
||||
crossterm = "0.28.1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.12", features = ["codec"] }
|
||||
soundcloud = { git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||
itunesdb = { git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
soundcloud = { version = "0.1.1", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
|
||||
itunesdb = { version = "0.1.1", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
|
@ -1,12 +1,12 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct YouTubeConfiguration {
|
||||
pub struct YouTubeConfiguration {
|
||||
pub user_id: u64
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SoundCloudConfiguration {
|
||||
pub struct SoundCloudConfiguration {
|
||||
pub user_id: u64
|
||||
}
|
||||
|
||||
|
107
src/main.rs
107
src/main.rs
@ -1,15 +1,21 @@
|
||||
use std::{error::Error, io, path::{Path, PathBuf}};
|
||||
|
||||
use color_eyre::Result;
|
||||
use config::LyricaConfiguration;
|
||||
use crossterm::{event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyEventKind}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}};
|
||||
use ratatui::{buffer::Buffer, layout::Rect, prelude::{Backend, CrosstermBackend}, style::Stylize, symbols::border, text::{Line, Text}, widgets::{Block, Paragraph, Widget}, DefaultTerminal, Frame, Terminal};
|
||||
use ratatui::{buffer::Buffer, layout::{Layout, Rect}, prelude::{Backend, CrosstermBackend}, style::{Color, Stylize}, symbols::border, text::{Line, Text}, widgets::{Block, Paragraph, Tabs, Widget}, DefaultTerminal, Frame, Terminal};
|
||||
use screen::MainScreen;
|
||||
use soundcloud::sobjects::CloudPlaylists;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::{fs::File, io::AsyncReadExt, sync::mpsc::{self, Receiver, Sender, UnboundedReceiver, UnboundedSender}};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use itunesdb::xobjects::XDatabase;
|
||||
use ratatui::prelude::Constraint::{Length, Min};
|
||||
|
||||
mod util;
|
||||
mod config;
|
||||
mod tabs;
|
||||
mod screen;
|
||||
|
||||
fn get_configs_dir() -> PathBuf {
|
||||
let mut p = dirs::home_dir().unwrap();
|
||||
@ -20,10 +26,7 @@ fn get_configs_dir() -> PathBuf {
|
||||
#[derive(Debug, Clone)]
|
||||
enum AppState {
|
||||
IPodWait,
|
||||
MainScreen(String),
|
||||
SoundCloud,
|
||||
Youtube,
|
||||
Preferences
|
||||
MainScreen(crate::screen::MainScreen)
|
||||
}
|
||||
|
||||
enum AppEvent {
|
||||
@ -31,7 +34,8 @@ enum AppEvent {
|
||||
IPodFound(String),
|
||||
IPodNotFound,
|
||||
ParseItunes(String),
|
||||
ITunesParsed(XDatabase)
|
||||
ITunesParsed(XDatabase),
|
||||
SoundcloudGot(CloudPlaylists)
|
||||
}
|
||||
|
||||
fn initialize_async_service(sender: Sender<AppEvent>, receiver: UnboundedReceiver<AppEvent>, token: CancellationToken) {
|
||||
@ -44,31 +48,43 @@ fn initialize_async_service(sender: Sender<AppEvent>, receiver: UnboundedReceive
|
||||
if let Some(request) = r {
|
||||
match request {
|
||||
AppEvent::SearchIPod => {
|
||||
if let Some(p) = util::search_ipod() {
|
||||
/*if let Some(p) = util::search_ipod() {
|
||||
let _ = sender.send(AppEvent::IPodFound(p)).await;
|
||||
} else {
|
||||
let _ = sender.send(AppEvent::IPodNotFound).await;
|
||||
}
|
||||
}*/
|
||||
let _ = sender.send(AppEvent::IPodFound("D:\\Documents\\RustroverProjects\\itunesdb\\ITunesDB\\two_tracks".to_string())).await;
|
||||
},
|
||||
AppEvent::ParseItunes(path) => {
|
||||
// todo: parse itunes
|
||||
let _ = std::fs::create_dir_all(get_configs_dir());
|
||||
let mut cd = get_configs_dir();
|
||||
cd.push("idb");
|
||||
/*let mut p = get_configs_dir();
|
||||
p.push("config");
|
||||
p.set_extension(".toml");
|
||||
p.exists()*/
|
||||
let mut p: PathBuf = Path::new(&path).into();
|
||||
p.push("iPod_Control");
|
||||
p.push("iTunes");
|
||||
p.set_file_name("iTunesDB");
|
||||
// p.push("iPod_Control");
|
||||
// p.push("iTunes");
|
||||
// p.set_file_name("iTunesDB");
|
||||
let _ = std::fs::copy(p, &cd);
|
||||
let mut file = File::open(cd).await.unwrap();
|
||||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents).await.unwrap();
|
||||
let xdb = itunesdb::deserializer::parse_bytes(&contents);
|
||||
let _ = sender.send(AppEvent::ITunesParsed(xdb)).await;
|
||||
|
||||
let mut p = get_configs_dir();
|
||||
p.push("config");
|
||||
p.set_extension(".toml");
|
||||
if !p.exists() { return; }
|
||||
let mut file = File::open(p).await.unwrap();
|
||||
let mut content = String::new();
|
||||
file.read_to_string(&mut content).await.unwrap();
|
||||
let config: LyricaConfiguration = toml::from_str(&content).unwrap();
|
||||
|
||||
let app_version = soundcloud::get_app().await.unwrap().unwrap();
|
||||
let client_id = soundcloud::get_client_id().await.unwrap().unwrap();
|
||||
let playlists = soundcloud::get_playlists(config.get_soundcloud().user_id, client_id, app_version).await.unwrap();
|
||||
|
||||
let _ = sender.send(AppEvent::SoundcloudGot(playlists)).await;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@ -121,11 +137,21 @@ impl App {
|
||||
if let Ok(event) = self.receiver.try_recv() {
|
||||
match event {
|
||||
AppEvent::IPodFound(path) => {
|
||||
self.state = AppState::MainScreen(path.clone());
|
||||
self.state = AppState::MainScreen(MainScreen::new());
|
||||
let _ = self.sender.send(AppEvent::ParseItunes(path));
|
||||
},
|
||||
AppEvent::IPodNotFound => {
|
||||
let _ = self.sender.send(AppEvent::SearchIPod);
|
||||
},
|
||||
AppEvent::ITunesParsed(xdb) => {
|
||||
|
||||
},
|
||||
AppEvent::SoundcloudGot(playlists) => {
|
||||
if let AppState::MainScreen(screen) = &self.state {
|
||||
let mut screen = screen.clone();
|
||||
screen.soundcloud = Some(playlists);
|
||||
self.state = AppState::MainScreen(screen);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@ -134,8 +160,14 @@ impl App {
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
if key_event.code == KeyCode::Char('q') {
|
||||
self.exit();
|
||||
if let AppState::MainScreen(screen) = &self.state {
|
||||
let mut screen = screen.clone();
|
||||
screen.handle_key_event(key_event);
|
||||
self.state = AppState::MainScreen(screen);
|
||||
}
|
||||
match key_event.code {
|
||||
KeyCode::Char('q') => self.exit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,32 +177,17 @@ impl App {
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn render_main_screen(area: Rect, buf: &mut Buffer, path: String) {
|
||||
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 counter_text = Text::from(
|
||||
vec![
|
||||
Line::from(
|
||||
vec![
|
||||
"Parsing iTunesDB...".into(),
|
||||
path.blue().bold()
|
||||
]
|
||||
)
|
||||
]
|
||||
);
|
||||
fn render_main_screen(area: Rect, buf: &mut Buffer, screen: &mut MainScreen) {
|
||||
let vertical = Layout::vertical([Length(1), Min(0), Length(1)]);
|
||||
let [header_area, inner_area, footer_area] = vertical.areas(area);
|
||||
|
||||
Paragraph::new(counter_text)
|
||||
.centered()
|
||||
.block(block)
|
||||
.render(area, buf);
|
||||
let horizontal = Layout::horizontal([Min(0), Length(7)]);
|
||||
let [tabs_area, title_area] = horizontal.areas(header_area);
|
||||
|
||||
MainScreen::render_title(title_area, buf);
|
||||
screen.render_tabs(tabs_area, buf);
|
||||
screen.selected_tab.render(inner_area, buf);
|
||||
MainScreen::render_footer(footer_area, buf);
|
||||
}
|
||||
|
||||
fn render_waiting_screen(area: Rect, buf: &mut Buffer) {
|
||||
@ -205,7 +222,7 @@ impl Widget for AppState {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
match self {
|
||||
AppState::IPodWait => AppState::render_waiting_screen(area, buf),
|
||||
AppState::MainScreen(s) => AppState::render_main_screen(area, buf, s),
|
||||
AppState::MainScreen(mut s) => AppState::render_main_screen(area, buf, &mut s),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
56
src/screen.rs
Normal file
56
src/screen.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::{Color, Stylize}, text::Line, widgets::{Tabs, Widget}};
|
||||
use soundcloud::sobjects::CloudPlaylists;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::tabs::SelectedTab;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MainScreen {
|
||||
pub selected_tab: SelectedTab,
|
||||
pub soundcloud: Option<CloudPlaylists>
|
||||
}
|
||||
|
||||
impl MainScreen {
|
||||
pub fn new() -> Self {
|
||||
MainScreen { selected_tab: SelectedTab::Playlists, soundcloud: None }
|
||||
}
|
||||
|
||||
pub fn handle_key_event(&mut self, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
KeyCode::Char('l') | KeyCode::Right => self.next_tab(),
|
||||
KeyCode::Char('h') | KeyCode::Left => self.previous_tab(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_title(area: Rect, buf: &mut Buffer) {
|
||||
"Lyrica".bold().render(area, buf);
|
||||
}
|
||||
|
||||
pub fn render_footer(area: Rect, buf: &mut Buffer) {
|
||||
Line::raw("◄ ► to change tab | <Q> to quit")
|
||||
.centered()
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
pub fn render_tabs(&self, area: Rect, buf: &mut Buffer) {
|
||||
let titles = SelectedTab::iter().map(SelectedTab::title);
|
||||
let highlight_style = (Color::default(), self.selected_tab.palette().c700);
|
||||
let selected_tab_index = self.selected_tab.to_usize();
|
||||
Tabs::new(titles)
|
||||
.highlight_style(highlight_style)
|
||||
.select(selected_tab_index)
|
||||
.padding("", "")
|
||||
.divider(" ")
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn next_tab(&mut self) {
|
||||
self.selected_tab = self.selected_tab.next();
|
||||
}
|
||||
|
||||
fn previous_tab(&mut self) {
|
||||
self.selected_tab = self.selected_tab.previous();
|
||||
}
|
||||
}
|
97
src/tabs.rs
Normal file
97
src/tabs.rs
Normal file
@ -0,0 +1,97 @@
|
||||
use ratatui::{buffer::Buffer, layout::Rect, style::{palette::tailwind, Stylize}, symbols, text::Line, widgets::{Block, Padding, Paragraph, Widget}};
|
||||
use soundcloud::sobjects::CloudPlaylists;
|
||||
use strum::{AsRefStr, Display, EnumIter, FromRepr, IntoEnumIterator};
|
||||
|
||||
use crate::screen::MainScreen;
|
||||
|
||||
#[derive(Debug, Default, Clone, Display, FromRepr, EnumIter, AsRefStr)]
|
||||
pub enum SelectedTab {
|
||||
#[default]
|
||||
#[strum(to_string = "Playlists")]
|
||||
Playlists,
|
||||
#[strum(to_string = "Albums")]
|
||||
Albums,
|
||||
#[strum(to_string = "Soundcloud")]
|
||||
Soundcloud(Option<CloudPlaylists>),
|
||||
#[strum(to_string = "Youtube")]
|
||||
Youtube,
|
||||
}
|
||||
|
||||
impl Widget for SelectedTab {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let block = self.block();
|
||||
match self {
|
||||
Self::Albums => self.render_albums(area, buf),
|
||||
Self::Playlists => self.render_playlists(area, buf),
|
||||
Self::Soundcloud(playlists) => SelectedTab::render_soundcloud(block,area, buf, playlists),
|
||||
Self::Youtube => self.render_youtube(area, buf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectedTab {
|
||||
/// Return tab's name as a styled `Line`
|
||||
pub fn title(self) -> Line<'static> {
|
||||
format!(" {self} ")
|
||||
.fg(tailwind::SLATE.c200)
|
||||
.bg(self.palette().c900)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_albums(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Hello, World!")
|
||||
.block(self.block())
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_playlists(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("Welcome to the Ratatui tabs example!")
|
||||
.block(self.block())
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_soundcloud(block: Block<'static>, area: Rect, buf: &mut Buffer, playlists: Option<CloudPlaylists>) {
|
||||
Paragraph::new("Your playlists from soundcloud:")
|
||||
.block(block)
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
fn render_youtube(self, area: Rect, buf: &mut Buffer) {
|
||||
Paragraph::new("I know, these are some basic changes. But I think you got the main idea.")
|
||||
.block(self.block())
|
||||
.render(area, buf);
|
||||
}
|
||||
|
||||
/// A block surrounding the tab's content
|
||||
fn block(&self) -> Block<'static> {
|
||||
Block::bordered()
|
||||
.border_set(symbols::border::THICK)
|
||||
.padding(Padding::horizontal(1))
|
||||
.border_style(self.palette().c700)
|
||||
}
|
||||
|
||||
pub fn palette(&self) -> tailwind::Palette {
|
||||
match self {
|
||||
Self::Albums => tailwind::INDIGO,
|
||||
Self::Playlists => tailwind::EMERALD,
|
||||
Self::Soundcloud(_) => tailwind::ORANGE,
|
||||
Self::Youtube => tailwind::RED,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(self) -> Self {
|
||||
let current_index = self.clone().to_usize();
|
||||
let previous_index = current_index.saturating_sub(1);
|
||||
Self::from_repr(previous_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
pub fn next(self) -> Self {
|
||||
let current_index = self.clone().to_usize();
|
||||
let next_index = current_index.saturating_add(1);
|
||||
Self::from_repr(next_index).unwrap_or(self)
|
||||
}
|
||||
|
||||
pub fn to_usize(self) -> usize {
|
||||
SelectedTab::iter().enumerate().find(|(_i, el)| el.as_ref() == self.as_ref()).unwrap().0
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user