[work]: settings system for genius_token setup
The last thing needed is the ui for searching songs
This commit is contained in:
parent
2c72b9f6a2
commit
cae76c8d72
3 changed files with 192 additions and 74 deletions
|
|
@ -1,22 +1,18 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
#[allow(unused)]
|
||||
use crate::core::settings;
|
||||
use crate::core::songs::{Song, VerseName};
|
||||
use itertools::Itertools;
|
||||
use miette::{IntoDiagnostic, Result, miette};
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::{tag, take_till, take_till1, take_until};
|
||||
use nom::character::complete::{
|
||||
digit0, multispace0, newline, space0,
|
||||
};
|
||||
use nom::combinator::{complete, eof, peek, rest};
|
||||
use nom::multi::{many0, many1, separated_list1};
|
||||
use nom::sequence::{delimited, pair, preceded, terminated};
|
||||
use nom::character::complete::{digit0, space0};
|
||||
use nom::combinator::rest;
|
||||
use nom::multi::many1;
|
||||
use nom::sequence::{delimited, pair};
|
||||
use nom::{IResult, Parser};
|
||||
use reqwest::header;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
|
|
@ -56,6 +52,18 @@ pub enum Provider {
|
|||
LyricsCom,
|
||||
}
|
||||
|
||||
impl Display for Provider {
|
||||
fn fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Provider::Genius { .. } => f.write_str("Genius"),
|
||||
Provider::LyricsCom => f.write_str("Lyrics.com"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OnlineSong> for Song {
|
||||
fn from(online_song: OnlineSong) -> Self {
|
||||
let map = if online_song.provider
|
||||
|
|
@ -66,17 +74,18 @@ impl From<OnlineSong> for Song {
|
|||
)
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
let mut map = HashMap::new();
|
||||
map.entry(VerseName::Verse { number: 1 })
|
||||
.or_insert(online_song.lyrics);
|
||||
Some(map)
|
||||
};
|
||||
|
||||
let song = Self {
|
||||
Self {
|
||||
title: online_song.title,
|
||||
author: Some(online_song.author),
|
||||
verse_map: map,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
song
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -86,8 +95,6 @@ fn parse_genius_lyrics(
|
|||
) -> Result<HashMap<VerseName, String>> {
|
||||
let (input, chunks) =
|
||||
many1(pair(parse_verse_name, alt((take_until("["), rest))))
|
||||
// separated_list1(pair(newline, newline), ident)
|
||||
// many1(complete(take_until("[")))
|
||||
.parse(lyrics)
|
||||
.map_err(|e| e.to_owned())
|
||||
.into_diagnostic()?;
|
||||
|
|
@ -102,38 +109,12 @@ fn parse_genius_lyrics(
|
|||
name = name.next();
|
||||
}
|
||||
|
||||
map.entry(name).or_insert(lyric.trim().to_string());
|
||||
map.entry(name).or_insert_with(|| lyric.trim().to_string());
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn ident(input: &str) -> IResult<&str, &str> {
|
||||
dbg!(&input);
|
||||
preceded(space0, any).parse(input)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn any(input: &str) -> IResult<&str, &str> {
|
||||
dbg!(&input);
|
||||
complete(take_until("\n\n")).parse(input)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn block(input: &str) -> IResult<&str, &str> {
|
||||
dbg!(&input);
|
||||
terminated(ident, pair(newline, newline)).parse(input)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn parse_verse(chunk: &str) -> IResult<&str, (VerseName, String)> {
|
||||
let (input, verse_name) = parse_verse_name.parse(chunk)?;
|
||||
let lyrics = input.trim().to_string();
|
||||
Ok((input, (verse_name, lyrics)))
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn parse_verse_name(line: &str) -> IResult<&str, VerseName> {
|
||||
let (input, (name, _, num, _, _)) = delimited(
|
||||
(tag("["), space0),
|
||||
|
|
@ -167,7 +148,7 @@ fn parse_verse_name(line: &str) -> IResult<&str, VerseName> {
|
|||
}
|
||||
|
||||
pub async fn search_genius_links(
|
||||
query: impl AsRef<str> + std::fmt::Display,
|
||||
query: String,
|
||||
auth_token: String,
|
||||
) -> Result<Vec<OnlineSong>> {
|
||||
// let Some(auth_token) = option_env!("GENIUS_TOKEN") else {
|
||||
|
|
@ -273,7 +254,7 @@ pub async fn get_genius_lyrics(
|
|||
);
|
||||
let lyrics = lyrics.replace("<br>", "\n");
|
||||
song.provider = Provider::Genius {
|
||||
parsable: lyrics.contains("["),
|
||||
parsable: lyrics.contains('['),
|
||||
};
|
||||
song.lyrics = lyrics;
|
||||
Ok(song)
|
||||
|
|
@ -391,7 +372,7 @@ mod test {
|
|||
link: "https://genius.com/North-point-worship-death-was-arrested-lyrics".to_string(),
|
||||
};
|
||||
let hits = search_genius_links(
|
||||
"Death was arrested",
|
||||
"Death was arrested".to_string(),
|
||||
env!("GENIUS_TOKEN").to_string(),
|
||||
)
|
||||
.await
|
||||
|
|
@ -529,7 +510,6 @@ mod test {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
#[test]
|
||||
fn test_parse_song() -> Result<()> {
|
||||
let song = r#"[Verse 1]
|
||||
|
|
@ -588,24 +568,6 @@ Aw man, that was good"#;
|
|||
let new_map = parse_genius_lyrics(&new_song)?;
|
||||
dbg!(map);
|
||||
dbg!(new_map);
|
||||
panic!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_parsing() -> Result<()> {
|
||||
let chorus = r#"[Chorus]
|
||||
I'm singing, "Hallelujah" (Hallelujah)
|
||||
God is able, hallelujah (Hallelujah)
|
||||
God is faithful, hallelujah
|
||||
Lord, I'm gonna sing (Come on now, sing it)
|
||||
Oh I'm singing, "Hallelujah" (Hallelujah)
|
||||
God is able, hallelujah (Hallelujah)
|
||||
God is faithful, hallelujah (God is so good)
|
||||
Lord, I'm gonna sing (Sing it, Dave)
|
||||
|
||||
"#;
|
||||
let _thing = block.parse(chorus).into_diagnostic()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
61
src/main.rs
61
src/main.rs
|
|
@ -176,6 +176,7 @@ struct App {
|
|||
config_handler: Option<Config>,
|
||||
obs_connection: String,
|
||||
view_mode: ViewMode,
|
||||
genius_token_hidden: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
@ -231,6 +232,8 @@ enum Message {
|
|||
SetObsConnection(String),
|
||||
ModifiersPressed(Modifiers),
|
||||
ViewModeSwitch(ViewMode),
|
||||
ShowGeniusToken,
|
||||
SetGeniusToken(String),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
|
@ -353,7 +356,10 @@ impl cosmic::Application for App {
|
|||
let items: Arc<Vec<ServiceItem>> = Arc::new(vec![]);
|
||||
|
||||
let presenter = Presenter::with_items(items.clone());
|
||||
let song_editor = SongEditor::new(Arc::clone(&fontdb));
|
||||
let song_editor = SongEditor::new(
|
||||
Arc::clone(&fontdb),
|
||||
settings.genius_token.clone(),
|
||||
);
|
||||
|
||||
// for item in items.iter() {
|
||||
// nav_model.insert().text(item.title()).data(item.clone());
|
||||
|
|
@ -433,6 +439,7 @@ impl cosmic::Application for App {
|
|||
config_handler,
|
||||
obs_connection: String::new(),
|
||||
view_mode: ViewMode::Row,
|
||||
genius_token_hidden: true,
|
||||
};
|
||||
|
||||
let mut batch = vec![];
|
||||
|
|
@ -826,6 +833,20 @@ impl cosmic::Application for App {
|
|||
Message::SetObsUrl(self.obs_connection.clone()),
|
||||
),
|
||||
);
|
||||
let genius_token = settings::item::builder("Token")
|
||||
.control(
|
||||
text_input::secure_input(
|
||||
"",
|
||||
self.settings
|
||||
.genius_token
|
||||
.clone()
|
||||
.unwrap_or_default(),
|
||||
Some(Message::ShowGeniusToken),
|
||||
self.genius_token_hidden,
|
||||
)
|
||||
.select_on_focus(true)
|
||||
.on_input(Message::SetGeniusToken),
|
||||
);
|
||||
let settings_column = column![
|
||||
icon::from_name("dialog-close")
|
||||
.symbolic(true)
|
||||
|
|
@ -837,17 +858,25 @@ impl cosmic::Application for App {
|
|||
.padding(space_s)
|
||||
.align_right(Length::Fill)
|
||||
.align_top(60),
|
||||
horizontal().height(space_xxl),
|
||||
settings::section()
|
||||
.title("Obs Settings")
|
||||
.add(obs_socket)
|
||||
.add(apply_button)
|
||||
.apply(container)
|
||||
.center_x(Length::Fill)
|
||||
.align_top(Length::Fill)
|
||||
.padding([0, space_xxxl * 2])
|
||||
.align_top(Length::Fill),
|
||||
settings::section()
|
||||
.title("Genius Auth Token")
|
||||
.add(genius_token)
|
||||
.apply(container)
|
||||
.center_x(Length::Fill)
|
||||
.align_top(Length::Fill),
|
||||
horizontal().height(space_xxl),
|
||||
]
|
||||
.height(Length::Fill);
|
||||
.spacing(space_s)
|
||||
.height(Length::Fill)
|
||||
.apply(container)
|
||||
.padding(space_xxl);
|
||||
let settings_container = settings_column
|
||||
.apply(container)
|
||||
.style(nav_bar_style)
|
||||
|
|
@ -1560,9 +1589,31 @@ impl cosmic::Application for App {
|
|||
Task::none()
|
||||
}
|
||||
Message::SetObsConnection(url) => {
|
||||
if let Some(config_handler) =
|
||||
self.config_handler.as_ref()
|
||||
{
|
||||
// todo!()
|
||||
();
|
||||
}
|
||||
self.obs_connection = url;
|
||||
Task::none()
|
||||
}
|
||||
Message::SetGeniusToken(token) => {
|
||||
if let Some(config_handler) =
|
||||
self.config_handler.as_ref()
|
||||
{
|
||||
self.settings.set_genius_token(
|
||||
config_handler,
|
||||
Some(token.clone()),
|
||||
);
|
||||
self.song_editor.genius_token = Some(token);
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::ShowGeniusToken => {
|
||||
self.genius_token_hidden = !self.genius_token_hidden;
|
||||
Task::none()
|
||||
}
|
||||
Message::ModifiersPressed(modifiers) => {
|
||||
if modifiers.is_empty() {
|
||||
self.modifiers_pressed = None;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ use std::io::{self};
|
|||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cosmic::cosmic_config::CosmicConfigEntry;
|
||||
use cosmic::dialog::file_chooser::FileFilter;
|
||||
use cosmic::dialog::file_chooser::open::Dialog;
|
||||
use cosmic::iced::alignment::{Horizontal, Vertical};
|
||||
use cosmic::iced::core::widget::tree;
|
||||
use cosmic::iced::font::{Style, Weight};
|
||||
use cosmic::iced::theme::Base;
|
||||
use cosmic::iced::widget::scrollable::{
|
||||
self as iced_scrollable, AbsoluteOffset, Direction, Scrollbar,
|
||||
};
|
||||
|
|
@ -18,6 +20,7 @@ use cosmic::iced::{
|
|||
Background as ContainerBackground, Border, Color, Length,
|
||||
Padding, Shadow, Vector, color, task,
|
||||
};
|
||||
use cosmic::widget::button::Catalog;
|
||||
use cosmic::widget::color_picker::ColorPickerUpdate;
|
||||
use cosmic::widget::grid::{self};
|
||||
use cosmic::widget::space::{self, horizontal};
|
||||
|
|
@ -36,7 +39,9 @@ use itertools::Itertools;
|
|||
use tracing::{debug, error};
|
||||
|
||||
use crate::core::service_items::ServiceTrait;
|
||||
use crate::core::settings;
|
||||
use crate::core::slide::{Slide, TextAlignment};
|
||||
use crate::core::song_search::{self, OnlineSong};
|
||||
use crate::core::songs::{Song, VerseName};
|
||||
use crate::ui::presenter::slide_view;
|
||||
use crate::ui::slide_editor::SlideEditor;
|
||||
|
|
@ -89,6 +94,9 @@ pub struct SongEditor {
|
|||
shadow_color_model: ColorPickerModel,
|
||||
shadow_tools_open: bool,
|
||||
importing: bool,
|
||||
search_input: String,
|
||||
search_results: Option<Vec<OnlineSong>>,
|
||||
pub genius_token: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
|
|
@ -143,6 +151,9 @@ pub enum Message {
|
|||
UpdateShadowOffsetX(usize),
|
||||
UpdateShadowOffsetY(usize),
|
||||
ChangeFontWeight,
|
||||
SearchUpdate(String),
|
||||
SearchSong(String),
|
||||
UpdateSearchResults(Result<Vec<OnlineSong>, String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -182,7 +193,10 @@ impl Display for Face {
|
|||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
impl SongEditor {
|
||||
pub fn new(font_db: Arc<fontdb::Database>) -> Self {
|
||||
pub fn new(
|
||||
font_db: Arc<fontdb::Database>,
|
||||
genius_token: Option<String>,
|
||||
) -> Self {
|
||||
let fonts = font_dir();
|
||||
debug!(?fonts);
|
||||
let fonts: Vec<Face> = font_db
|
||||
|
|
@ -337,6 +351,9 @@ impl SongEditor {
|
|||
),
|
||||
shadow_tools_open: false,
|
||||
importing: false,
|
||||
search_input: String::new(),
|
||||
search_results: None,
|
||||
genius_token,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -923,6 +940,28 @@ impl SongEditor {
|
|||
Message::OpenShadowTools => {
|
||||
self.shadow_tools_open = !self.shadow_tools_open;
|
||||
}
|
||||
Message::SearchUpdate(query) => {
|
||||
self.search_input = query;
|
||||
}
|
||||
Message::SearchSong(query) => {
|
||||
return Action::Task(Task::perform(
|
||||
song_search::search_genius_links(
|
||||
query,
|
||||
self.genius_token.clone().unwrap_or_default(),
|
||||
),
|
||||
|res| {
|
||||
Message::UpdateSearchResults(
|
||||
res.map_err(|e| e.to_string()),
|
||||
)
|
||||
},
|
||||
));
|
||||
}
|
||||
Message::UpdateSearchResults(result) => match result {
|
||||
Ok(songs) => self.search_results = Some(songs),
|
||||
Err(e) => {
|
||||
error!("Cannot find songs: {e}");
|
||||
}
|
||||
},
|
||||
Message::None => (),
|
||||
}
|
||||
Action::None
|
||||
|
|
@ -1852,7 +1891,73 @@ impl SongEditor {
|
|||
}
|
||||
|
||||
pub fn import_view(&self) -> Element<Message> {
|
||||
todo!("need to add an import view")
|
||||
let search_bar =
|
||||
cosmic::widget::search_input("", &self.search_input)
|
||||
.on_input(Message::SearchUpdate)
|
||||
.on_submit(Message::SearchSong)
|
||||
.label("Search for Song");
|
||||
let submit_button =
|
||||
button::icon(icon::from_name("document-send-symbolic"))
|
||||
.on_press(Message::SearchSong(
|
||||
self.search_input.clone(),
|
||||
));
|
||||
|
||||
let search_results =
|
||||
self.search_results.as_ref().map_or_else(
|
||||
|| space::horizontal().apply(container),
|
||||
|songs| {
|
||||
let songs: Vec<Element<Message>> = songs
|
||||
.iter()
|
||||
.map(|song| {
|
||||
let title = text::heading(&song.title);
|
||||
let author = text::body(&song.author);
|
||||
let link = text::body(&song.link);
|
||||
let provider =
|
||||
text::body(song.provider.to_string())
|
||||
.apply(container)
|
||||
.style(|t| {
|
||||
container::Style::default()
|
||||
.color(
|
||||
t.cosmic()
|
||||
.palette
|
||||
.accent_green,
|
||||
)
|
||||
.border(
|
||||
Border::default()
|
||||
.rounded(
|
||||
t.cosmic()
|
||||
.radius_s(
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
.padding(
|
||||
theme::spacing().space_s,
|
||||
);
|
||||
row![
|
||||
column![title, author, link].spacing(
|
||||
theme::spacing().space_xxs
|
||||
),
|
||||
space::horizontal(),
|
||||
provider
|
||||
]
|
||||
.padding(theme::spacing().space_s)
|
||||
.apply(container)
|
||||
.class(theme::Container::Card)
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
column::with_children(songs)
|
||||
.spacing(theme::spacing().space_s)
|
||||
.apply(container)
|
||||
},
|
||||
);
|
||||
|
||||
let search_row =
|
||||
row![search_bar, space::horizontal(), submit_button];
|
||||
|
||||
column![search_row, search_results].into()
|
||||
}
|
||||
|
||||
pub const fn editing(&self) -> bool {
|
||||
|
|
@ -2045,7 +2150,7 @@ impl Default for SongEditor {
|
|||
fn default() -> Self {
|
||||
let mut fontdb = fontdb::Database::new();
|
||||
fontdb.load_system_fonts();
|
||||
Self::new(Arc::new(fontdb))
|
||||
Self::new(Arc::new(fontdb), None)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue