using a better set of data models in songs
This commit is contained in:
parent
a8027ec7b9
commit
6e4fe70838
3 changed files with 162 additions and 137 deletions
|
|
@ -35,110 +35,80 @@ pub struct Song {
|
|||
pub font: Option<String>,
|
||||
pub font_size: Option<i32>,
|
||||
pub stroke_size: Option<i32>,
|
||||
pub verses: Option<Vec<Verse>>,
|
||||
pub verses: Option<Vec<VerseName>>,
|
||||
pub verse_map: Option<HashMap<VerseName, String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Verse {
|
||||
Verse { number: usize, lyric: String },
|
||||
PreChorus { number: usize, lyric: String },
|
||||
Chorus { number: usize, lyric: String },
|
||||
PostChorus { number: usize, lyric: String },
|
||||
Bridge { number: usize, lyric: String },
|
||||
Intro { number: usize, lyric: String },
|
||||
Outro { number: usize, lyric: String },
|
||||
Instrumental { number: usize, lyric: String },
|
||||
Other { number: usize, lyric: String },
|
||||
#[derive(
|
||||
Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Hash,
|
||||
)]
|
||||
pub enum VerseName {
|
||||
Verse { number: usize },
|
||||
PreChorus { number: usize },
|
||||
Chorus { number: usize },
|
||||
PostChorus { number: usize },
|
||||
Bridge { number: usize },
|
||||
Intro { number: usize },
|
||||
Outro { number: usize },
|
||||
Instrumental { number: usize },
|
||||
Other { number: usize },
|
||||
}
|
||||
|
||||
impl Verse {
|
||||
impl VerseName {
|
||||
pub fn get_name(&self) -> String {
|
||||
match self {
|
||||
Verse::Verse { number, .. } => {
|
||||
VerseName::Verse { number, .. } => {
|
||||
let mut string = "Verse ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::PreChorus { number, .. } => {
|
||||
VerseName::PreChorus { number, .. } => {
|
||||
let mut string = "Pre-Chorus ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Chorus { number, .. } => {
|
||||
VerseName::Chorus { number, .. } => {
|
||||
let mut string = "Chorus ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::PostChorus { number, .. } => {
|
||||
VerseName::PostChorus { number, .. } => {
|
||||
let mut string = "Post-Chorus ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Bridge { number, .. } => {
|
||||
VerseName::Bridge { number, .. } => {
|
||||
let mut string = "Bridge ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Intro { number, .. } => {
|
||||
VerseName::Intro { number, .. } => {
|
||||
let mut string = "Intro ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Outro { number, .. } => {
|
||||
VerseName::Outro { number, .. } => {
|
||||
let mut string = "Outro ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Instrumental { number, .. } => {
|
||||
VerseName::Instrumental { number, .. } => {
|
||||
let mut string = "Instrumental ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
Verse::Other { number, .. } => {
|
||||
VerseName::Other { number, .. } => {
|
||||
let mut string = "Other ".to_string();
|
||||
string.push_str(&number.to_string());
|
||||
string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_lyric(&self) -> String {
|
||||
match self {
|
||||
Verse::Verse { lyric, .. } => lyric.clone(),
|
||||
Verse::PreChorus { lyric, .. } => lyric.clone(),
|
||||
Verse::Chorus { lyric, .. } => lyric.clone(),
|
||||
Verse::PostChorus { lyric, .. } => lyric.clone(),
|
||||
Verse::Bridge { lyric, .. } => lyric.clone(),
|
||||
Verse::Intro { lyric, .. } => lyric.clone(),
|
||||
Verse::Outro { lyric, .. } => lyric.clone(),
|
||||
Verse::Instrumental { lyric, .. } => lyric.clone(),
|
||||
Verse::Other { lyric, .. } => lyric.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_lyrics(&mut self, lyrics: String) {
|
||||
match self {
|
||||
Verse::Verse { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::PreChorus { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::Chorus { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::PostChorus { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::Bridge { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::Intro { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::Outro { number: _, lyric } => *lyric = lyrics,
|
||||
Verse::Instrumental { number: _, lyric } => {
|
||||
*lyric = lyrics
|
||||
}
|
||||
Verse::Other { number: _, lyric } => *lyric = lyrics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Verse {
|
||||
impl Default for VerseName {
|
||||
fn default() -> Self {
|
||||
Self::Verse {
|
||||
number: 1,
|
||||
lyric: "".into(),
|
||||
}
|
||||
Self::Verse { number: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,6 +191,17 @@ const VERSE_KEYWORDS: [&str; 24] = [
|
|||
|
||||
impl FromRow<'_, SqliteRow> for Song {
|
||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||
let Some((verses, verse_map)) =
|
||||
lyrics_to_verse(row.try_get(8)?).ok()
|
||||
else {
|
||||
return Err(sqlx::Error::ColumnDecode {
|
||||
index: "8".into(),
|
||||
source: miette!(
|
||||
"Couldn't decode the song into verses"
|
||||
)
|
||||
.into(),
|
||||
});
|
||||
};
|
||||
Ok(Self {
|
||||
id: row.try_get(12)?,
|
||||
title: row.try_get(5)?,
|
||||
|
|
@ -267,7 +248,8 @@ impl FromRow<'_, SqliteRow> for Song {
|
|||
font: row.try_get(6)?,
|
||||
font_size: row.try_get(1)?,
|
||||
stroke_size: None,
|
||||
verses: lyrics_to_verse(row.try_get(8)?).ok(),
|
||||
verses: Some(verses),
|
||||
verse_map: Some(verse_map),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -281,8 +263,10 @@ impl From<Value> for Song {
|
|||
}
|
||||
}
|
||||
|
||||
fn lyrics_to_verse(lyrics: String) -> Result<Vec<Verse>> {
|
||||
let mut lyric_list = Vec::new();
|
||||
fn lyrics_to_verse(
|
||||
lyrics: String,
|
||||
) -> Result<(Vec<VerseName>, HashMap<VerseName, String>)> {
|
||||
let mut verse_list = Vec::new();
|
||||
if lyrics.is_empty() {
|
||||
return Err(miette!("There is no lyrics here"));
|
||||
}
|
||||
|
|
@ -307,6 +291,7 @@ fn lyrics_to_verse(lyrics: String) -> Result<Vec<Verse>> {
|
|||
}
|
||||
}
|
||||
lyric_map.insert(verse_title, lyric);
|
||||
let mut verse_map = HashMap::new();
|
||||
for (verse_name, lyric) in lyric_map {
|
||||
let mut verse_elements = verse_name.trim().split_whitespace();
|
||||
let verse_keyword = verse_elements.next();
|
||||
|
|
@ -323,51 +308,24 @@ fn lyrics_to_verse(lyrics: String) -> Result<Vec<Verse>> {
|
|||
};
|
||||
let index = index.parse::<usize>().into_diagnostic()?;
|
||||
let verse = match keyword {
|
||||
"Verse" => Verse::Verse {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Pre-Chorus" => Verse::PreChorus {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Chorus" => Verse::Chorus {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Post-Chorus" => Verse::PostChorus {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Bridge" => Verse::Bridge {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Intro" => Verse::Intro {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Outro" => Verse::Outro {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Instrumental" => Verse::Instrumental {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Other" => Verse::Other {
|
||||
number: index,
|
||||
lyric: lyric,
|
||||
},
|
||||
_ => Verse::Other {
|
||||
number: 99,
|
||||
lyric: lyric,
|
||||
},
|
||||
"Verse" => VerseName::Verse { number: index },
|
||||
"Pre-Chorus" => VerseName::PreChorus { number: index },
|
||||
"Chorus" => VerseName::Chorus { number: index },
|
||||
"Post-Chorus" => VerseName::PostChorus { number: index },
|
||||
"Bridge" => VerseName::Bridge { number: index },
|
||||
"Intro" => VerseName::Intro { number: index },
|
||||
"Outro" => VerseName::Outro { number: index },
|
||||
"Instrumental" => {
|
||||
VerseName::Instrumental { number: index }
|
||||
}
|
||||
"Other" => VerseName::Other { number: index },
|
||||
_ => VerseName::Other { number: 99 },
|
||||
};
|
||||
lyric_list.push(verse);
|
||||
verse_list.push(verse.clone());
|
||||
verse_map.insert(verse, lyric);
|
||||
}
|
||||
|
||||
Ok(lyric_list)
|
||||
Ok((verse_list, verse_map))
|
||||
}
|
||||
|
||||
pub fn lisp_to_song(list: Vec<Value>) -> Song {
|
||||
|
|
@ -725,6 +683,23 @@ pub async fn update_song_in_db(
|
|||
}
|
||||
|
||||
impl Song {
|
||||
pub fn get_lyric(&self, verse: &VerseName) -> Option<String> {
|
||||
self.verse_map
|
||||
.as_ref()
|
||||
.map(|verse_map| verse_map.get(verse).cloned())
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn set_lyrics<T: Into<String>>(
|
||||
&mut self,
|
||||
verse: &VerseName,
|
||||
lyrics: T,
|
||||
) {
|
||||
if let Some(verse_map) = self.verse_map.as_mut() {
|
||||
verse_map.entry(verse.clone()).or_insert(lyrics.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_lyrics(&self) -> Result<Vec<String>> {
|
||||
let mut lyric_list = Vec::new();
|
||||
if self.lyrics.is_none() {
|
||||
|
|
@ -801,7 +776,7 @@ impl Song {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_verse(&mut self, index: usize, verse: Verse) {
|
||||
pub fn update_verse(&mut self, index: usize, verse: VerseName) {
|
||||
if let Some(verses) = self.verses.as_mut() {
|
||||
if let Some(old_verse) = verses.get_mut(index) {
|
||||
*old_verse = verse;
|
||||
|
|
@ -997,7 +972,8 @@ You saved my soul"
|
|||
font: Some("Quicksand Bold".to_string()),
|
||||
font_size: Some(60),
|
||||
stroke_size: None,
|
||||
verses: vec![]
|
||||
verses: Some(vec![]),
|
||||
verse_map: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,16 @@ use crate::{
|
|||
core::{
|
||||
service_items::ServiceTrait,
|
||||
slide::Slide,
|
||||
songs::{Song, Verse},
|
||||
songs::{Song, VerseName},
|
||||
},
|
||||
ui::{
|
||||
presenter::slide_view,
|
||||
slide_editor::SlideEditor,
|
||||
text_svg,
|
||||
widgets::verse_editor::{self, VerseEditor},
|
||||
widgets::{
|
||||
draggable,
|
||||
verse_editor::{self, VerseEditor},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -47,6 +50,7 @@ pub struct SongEditor {
|
|||
verse_order: String,
|
||||
pub lyrics: text_editor::Content,
|
||||
editing: bool,
|
||||
editing_verses_order: bool,
|
||||
background: Option<Background>,
|
||||
video: Option<Video>,
|
||||
ccli: String,
|
||||
|
|
@ -150,6 +154,7 @@ impl SongEditor {
|
|||
stroke_size: 0,
|
||||
stroke_open: false,
|
||||
verses: None,
|
||||
editing_verses_order: false,
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Action {
|
||||
|
|
@ -208,9 +213,11 @@ impl SongEditor {
|
|||
},
|
||||
|slides| Message::UpdateSlides(slides),
|
||||
);
|
||||
self.verses = song.verses.map(|vec| {
|
||||
self.verses = song.verse_map.map(|vec| {
|
||||
vec.into_iter()
|
||||
.map(|verse| VerseEditor::new(verse))
|
||||
.map(|(verse_name, lyric)| {
|
||||
VerseEditor::new(verse_name, lyric)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
return Action::Task(task);
|
||||
|
|
@ -469,18 +476,35 @@ order",
|
|||
.spacing(5);
|
||||
|
||||
let verse_list = if let Some(verse_list) = &self.verses {
|
||||
Element::from(
|
||||
column(verse_list.into_iter().enumerate().map(
|
||||
|(index, v)| {
|
||||
v.view().map(move |message| {
|
||||
Message::VerseEditorMessage((
|
||||
index, message,
|
||||
))
|
||||
})
|
||||
},
|
||||
))
|
||||
.spacing(space_l),
|
||||
)
|
||||
if self.editing_verses_order {
|
||||
Element::from(
|
||||
draggable::column(
|
||||
verse_list.into_iter().enumerate().map(
|
||||
|(index, v)| {
|
||||
v.view(false).map(move |message| {
|
||||
Message::VerseEditorMessage((
|
||||
index, message,
|
||||
))
|
||||
})
|
||||
},
|
||||
),
|
||||
)
|
||||
.spacing(space_l),
|
||||
)
|
||||
} else {
|
||||
Element::from(
|
||||
column(verse_list.into_iter().enumerate().map(
|
||||
|(index, v)| {
|
||||
v.view(true).map(move |message| {
|
||||
Message::VerseEditorMessage((
|
||||
index, message,
|
||||
))
|
||||
})
|
||||
},
|
||||
))
|
||||
.spacing(space_l),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Element::from(horizontal_space())
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
use cosmic::{
|
||||
Apply, Element, Task,
|
||||
iced::{Length, alignment::Vertical},
|
||||
iced::{Border, Length, alignment::Vertical},
|
||||
iced_widget::{column, row},
|
||||
theme,
|
||||
widget::{container, icon, text, text_editor},
|
||||
};
|
||||
|
||||
use crate::core::songs::Verse;
|
||||
use crate::core::songs::VerseName;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VerseEditor {
|
||||
verse: Verse,
|
||||
verse_name: VerseName,
|
||||
lyric: String,
|
||||
content: text_editor::Content,
|
||||
}
|
||||
|
||||
|
|
@ -22,16 +23,16 @@ pub enum Message {
|
|||
|
||||
pub enum Action {
|
||||
Task(Task<Message>),
|
||||
UpdateVerse(Verse),
|
||||
UpdateVerse(VerseName),
|
||||
None,
|
||||
}
|
||||
|
||||
impl VerseEditor {
|
||||
pub fn new(verse: Verse) -> Self {
|
||||
let text = verse.get_lyric();
|
||||
pub fn new(verse: VerseName, lyric: String) -> Self {
|
||||
Self {
|
||||
verse,
|
||||
content: text_editor::Content::with_text(&text),
|
||||
verse_name: verse,
|
||||
lyric: lyric.clone(),
|
||||
content: text_editor::Content::with_text(&lyric),
|
||||
}
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Action {
|
||||
|
|
@ -39,15 +40,15 @@ impl VerseEditor {
|
|||
Message::ChangeText(action) => {
|
||||
self.content.perform(action);
|
||||
let lyrics = self.content.text();
|
||||
self.verse.set_lyrics(lyrics);
|
||||
let verse = self.verse.clone();
|
||||
self.lyric = lyrics;
|
||||
let verse = self.verse_name.clone();
|
||||
Action::UpdateVerse(verse)
|
||||
}
|
||||
Message::None => Action::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(&self) -> Element<Message> {
|
||||
pub fn view(&self, editable: bool) -> Element<Message> {
|
||||
let cosmic::cosmic_theme::Spacing {
|
||||
space_xxs,
|
||||
space_xs,
|
||||
|
|
@ -60,17 +61,41 @@ impl VerseEditor {
|
|||
} = theme::spacing();
|
||||
|
||||
let verse_title =
|
||||
text::heading(self.verse.get_name()).size(space_m);
|
||||
let editor = text_editor(&self.content)
|
||||
.on_action(Message::ChangeText)
|
||||
.padding(space_s)
|
||||
.height(Length::Fill);
|
||||
text::heading(self.verse_name.get_name()).size(space_m);
|
||||
let lyric: Element<Message> = if editable {
|
||||
text_editor(&self.content)
|
||||
.on_action(Message::ChangeText)
|
||||
.padding(space_s)
|
||||
.height(Length::Fill)
|
||||
// .style(|theme, status| {
|
||||
// let mut style =
|
||||
// text_editor::default(theme, status);
|
||||
// style.border = Border::default().rounded(space_s);
|
||||
// style
|
||||
// })
|
||||
.into()
|
||||
} else {
|
||||
text(self.lyric.clone())
|
||||
.apply(container)
|
||||
.center_y(Length::Fill)
|
||||
.width(Length::Fill)
|
||||
.style(move |t| {
|
||||
container::Style::default().border(
|
||||
Border::default()
|
||||
.rounded(space_s)
|
||||
.width(space_xxs)
|
||||
.color(t.cosmic().bg_component_divider()),
|
||||
)
|
||||
})
|
||||
.padding(space_s)
|
||||
.into()
|
||||
};
|
||||
|
||||
let drag_handle = icon::from_name("object-rows")
|
||||
.prefer_svg(true)
|
||||
.size(space_xxl);
|
||||
|
||||
let row = row![drag_handle, editor]
|
||||
let row = row![drag_handle, lyric]
|
||||
.spacing(space_s)
|
||||
.align_y(Vertical::Center);
|
||||
container(column![verse_title, row].spacing(space_s))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue