[feat]: Song importing works now
Some checks failed
/ clippy (push) Failing after 5m22s
/ test (push) Has been cancelled

This means we have a decent flow for creating and importing songs. By
default this will use Genius as the lyric backend. In the future we
will support more options, but for now this means you can get the
lyrics you need and start building songs rather fast.
This commit is contained in:
Chris Cochrun 2026-04-21 10:27:14 -05:00
parent d043caae27
commit e7d4c10ad6
5 changed files with 104 additions and 3 deletions

View file

@ -81,11 +81,29 @@ impl From<OnlineSong> for Song {
.or_insert(online_song.lyrics);
Some(map)
};
let lyrics = ron::ser::to_string(&map).ok();
let verse_order: Option<Vec<String>> =
if let Some(map) = map.as_ref() {
Some(map.keys().map(|v| v.get_name()).collect())
} else {
None
};
let verses: Option<Vec<VerseName>> =
if let Some(map) = map.as_ref() {
Some(map.keys().map(|v| v.to_owned()).collect())
} else {
None
};
Self {
title: online_song.title,
author: Some(online_song.author),
verse_map: map,
lyrics,
verse_order,
verses,
..Default::default()
}
}

View file

@ -788,6 +788,54 @@ pub async fn remove_song(
}
}
pub async fn insert_song(
mut song: Song,
mut songs: Vec<Song>,
db: Arc<SqlitePool>,
) -> Result<Vec<Song>> {
let verse_order = {
song.verse_order.clone().map_or_else(String::new, |vo| {
vo.into_iter()
.map(|mut s| {
s.push(' ');
s
})
.collect::<String>()
})
};
let audio = song
.audio
.clone()
.map(|a| a.to_str().unwrap_or_default().to_string());
let background = song
.background
.clone()
.map(|b| b.path.to_str().unwrap_or_default().to_string());
let res = query!(
r#"INSERT INTO songs (title, lyrics, author, ccli, verse_order, audio, font, font_size, background) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"#,
song.title,
song.lyrics,
song.author,
song.ccli,
verse_order,
audio,
song.font,
song.font_size,
background
)
.execute(&*db)
.await
.into_diagnostic()?;
song.id = i32::try_from(res.last_insert_rowid()).expect(
"Fairly confident that this number won't get that high",
);
songs.push(song);
Ok(songs)
}
pub async fn add_song(
mut songs: Vec<Song>,
db: Arc<SqlitePool>,

View file

@ -958,6 +958,17 @@ impl cosmic::Application for App {
Task::none()
}
}
song_editor::Action::AddSong(song) => {
if self.library.is_some() {
self.update(Message::Library(
library::Message::AddSongFromEditor(
song,
),
))
} else {
Task::none()
}
}
song_editor::Action::None => Task::none(),
}
}

View file

@ -29,7 +29,7 @@ use crate::core::kinds::ServiceItemKind;
use crate::core::model::{KindWrapper, LibraryKind, Model};
use crate::core::presentations::{self, Presentation};
use crate::core::service_items::ServiceItem;
use crate::core::songs::{self, Song};
use crate::core::songs::{self, Song, insert_song};
use crate::core::videos::{self, Video};
#[allow(clippy::struct_field_names)]
@ -110,6 +110,7 @@ pub enum Message {
ReaddVideos(Vec<Video>),
ReaddPres(Vec<Presentation>),
ToService((LibraryKind, i32)),
AddSongFromEditor(Song),
}
impl<'a> Library {
@ -185,6 +186,25 @@ impl<'a> Library {
Message::AddSong => {
return Action::CreateSong;
}
Message::AddSongFromEditor(song) => {
let after_task =
Task::done(Message::OpenItem(Some((
LibraryKind::Song,
self.song_library.items.len() as i32,
))));
debug!(?song);
let task = Task::perform(
insert_song(
song,
self.song_library.items.clone(),
Arc::clone(&self.db),
),
|res| {
res.map_or(Message::None, Message::ReaddSongs)
},
);
return Action::Task(task.chain(after_task));
}
Message::AddItem => {
let kind =
self.library_open.unwrap_or(LibraryKind::Song);

View file

@ -106,6 +106,7 @@ pub struct SongEditor {
pub enum Action {
Task(Task<Message>),
UpdateSong(Song),
AddSong(Song),
None,
}
@ -974,7 +975,8 @@ impl SongEditor {
}
Message::AddSong(song) => {
let song = Song::from(song);
return self.update(Message::ChangeSong(song));
self.importing = false;
return Action::AddSong(song);
}
Message::HoverSong(index) => {
self.hovered_online_song = index;
@ -1997,8 +1999,8 @@ impl SongEditor {
provider
]
.spacing(space_s)
.padding(space_m)
.apply(container)
.padding(space_m)
.style(move |theme| {
container::Style::default()
.background(
@ -2061,8 +2063,10 @@ impl SongEditor {
.collect();
column::with_children(songs)
.padding([space_s, space_l, space_s, space_s])
.spacing(space_s)
.apply(scrollable)
.scrollbar_padding(space_s)
.apply(container)
},
);