diff --git a/package.json b/package.json index 2b2f8e7..81be4c4 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "vite": "^4.1.1" }, "prettier": { + "useTabs": true, "printWidth": 100, "semi": false, "singleQuote": true, diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml index b196eaa..218e203 100644 --- a/src-tauri/rustfmt.toml +++ b/src-tauri/rustfmt.toml @@ -1 +1 @@ -tab_spaces = 2 +hard_tabs = true diff --git a/src-tauri/src/build.rs b/src-tauri/src/build.rs index 311900d..106b712 100644 --- a/src-tauri/src/build.rs +++ b/src-tauri/src/build.rs @@ -1,3 +1,3 @@ fn main() { - tauri_build::build(); + tauri_build::build(); } diff --git a/src-tauri/src/cmd.rs b/src-tauri/src/cmd.rs index c49322d..ea0819b 100644 --- a/src-tauri/src/cmd.rs +++ b/src-tauri/src/cmd.rs @@ -12,26 +12,26 @@ use tauri::{command, State}; #[derive(Clone, Serialize)] pub struct File { - pub path: PathBuf, - pub dirty: bool, - #[serde(skip_serializing)] - pub metadata: Metadata, + pub path: PathBuf, + pub dirty: bool, + #[serde(skip_serializing)] + pub metadata: Metadata, } #[derive(Default, Serialize)] pub struct App { - pub current_index: usize, - pub files: Vec, + pub current_index: usize, + pub files: Vec, } impl App { - pub fn current_file(&mut self) -> Result<&mut File, String> { - match self.files.get_mut(self.current_index) { - Some(file) => Ok(file), - None => { - throw!("Error getting open file") - } - } - } + pub fn current_file(&mut self) -> Result<&mut File, String> { + match self.files.get_mut(self.current_index) { + Some(file) => Ok(file), + None => { + throw!("Error getting open file") + } + } + } } #[derive(Default)] @@ -41,187 +41,187 @@ pub type AppArg<'a> = State<'a, AppState>; #[command] pub fn error_popup(msg: String, win: tauri::Window) { - println!("Error popup: {}", msg); - thread::spawn(move || { - dialog::message(Some(&win), "Error", msg); - }); + println!("Error popup: {}", msg); + thread::spawn(move || { + dialog::message(Some(&win), "Error", msg); + }); } #[command] pub fn get_app(app: AppArg<'_>) -> Value { - let app = app.0.lock().unwrap(); - serde_json::to_value(&*app).unwrap() + let app = app.0.lock().unwrap(); + serde_json::to_value(&*app).unwrap() } #[command] pub fn show(index: usize, app: AppArg<'_>) { - let mut app = app.0.lock().unwrap(); - app.current_index = index; + let mut app = app.0.lock().unwrap(); + app.current_index = index; } #[command] pub fn close_window(win: tauri::Window) { - let _ = win.close(); + let _ = win.close(); } fn id3_split<'a>(s: Option<&'a str>) -> Vec<&'a str> { - match s { - Some(s) => s.split('\u{0}').collect(), - None => vec![], - } + match s { + Some(s) => s.split('\u{0}').collect(), + None => vec![], + } } fn get_frame_text<'a>(tag: &'a id3::Tag, id: &str) -> Option<&'a str> { - let frame = tag.get(id)?; - let text = frame.content().text()?; - return Some(text); + let frame = tag.get(id)?; + let text = frame.content().text()?; + return Some(text); } fn opt_to_str<'a>(n: Option) -> String { - match n { - Some(n) => n.to_string(), - None => "".to_string(), - } + match n { + Some(n) => n.to_string(), + None => "".to_string(), + } } #[command] pub fn get_page(app: AppArg<'_>) -> Option { - let mut app = app.0.lock().unwrap(); - let file = app.current_file().ok()?; - - let title = match file.metadata { - Metadata::Id3(ref tag) => tag.title().unwrap_or("").to_string(), - Metadata::Mp4(ref tag) => tag.title().unwrap_or("").to_string(), - Metadata::VorbisComments(ref tag) => tag.title().map(|s| s.to_string()).unwrap_or_default(), - }; - - let artists: Vec<_> = match file.metadata { - Metadata::Id3(ref tag) => id3_split(tag.artist()), - Metadata::Mp4(ref tag) => tag.artists().collect(), - Metadata::VorbisComments(ref tag) => tag.get_all("ARTIST").collect(), - }; - - let album = match file.metadata { - Metadata::Id3(ref tag) => tag.album().unwrap_or("").to_string(), - Metadata::Mp4(ref tag) => tag.album().unwrap_or("").to_string(), - Metadata::VorbisComments(ref tag) => tag.album().map(|s| s.to_string()).unwrap_or_default(), - }; - - let album_artists: Vec<_> = match file.metadata { - Metadata::Id3(ref tag) => id3_split(tag.album_artist()), - Metadata::Mp4(ref tag) => tag.album_artists().collect(), - Metadata::VorbisComments(ref tag) => tag.get_all("ALBUMARTIST").collect(), - }; - - let composer = match file.metadata { - Metadata::Id3(ref tag) => id3_split(get_frame_text(&tag, "TCOM")), - Metadata::Mp4(ref tag) => tag.composers().collect(), - Metadata::VorbisComments(ref tag) => tag.get_all("COMPOSER").collect(), - }; - - let groupings = match file.metadata { - // non-standard iTunes tag, three-byte ID3v2.2 tag is not remapped - Metadata::Id3(ref tag) => id3_split(match get_frame_text(&tag, "GRP1") { - Some(s) => Some(s), - None => get_frame_text(&tag, "GP1"), - }), - Metadata::Mp4(ref tag) => tag.groupings().collect(), - Metadata::VorbisComments(ref tag) => tag.get_all("GROUPING").collect(), - }; - - let genres = match file.metadata { - Metadata::Id3(ref tag) => id3_split(tag.genre()), - Metadata::Mp4(ref tag) => tag.genres().collect(), - Metadata::VorbisComments(ref tag) => tag.get_all("GENRE").collect(), - }; - - let track_num = match file.metadata { - Metadata::Id3(ref tag) => opt_to_str(tag.track()), - Metadata::Mp4(ref tag) => opt_to_str(tag.track().0), - Metadata::VorbisComments(ref tag) => opt_to_str(tag.track()), - }; - let track_total = match file.metadata { - Metadata::Id3(ref tag) => opt_to_str(tag.total_tracks()), - Metadata::Mp4(ref tag) => opt_to_str(tag.track().1), - Metadata::VorbisComments(ref tag) => opt_to_str(tag.track_total()), - }; - - let disc_num = match file.metadata { - Metadata::Id3(ref tag) => opt_to_str(tag.disc()), - Metadata::Mp4(ref tag) => opt_to_str(tag.disc().0), - Metadata::VorbisComments(ref tag) => opt_to_str(tag.disk()), - }; - let disc_total = match file.metadata { - Metadata::Id3(ref tag) => opt_to_str(tag.total_discs()), - Metadata::Mp4(ref tag) => opt_to_str(tag.disc().1), - Metadata::VorbisComments(ref tag) => opt_to_str(tag.disk_total()), - }; - - let compilation = match file.metadata { - Metadata::Id3(ref tag) => { - // non-standard iTunes tag, three-byte ID3v2.2 tag is not remapped - get_frame_text(&tag, "TCMP") == Some("1") || get_frame_text(&tag, "TCP") == Some("1") - } - Metadata::Mp4(ref tag) => tag.compilation(), - Metadata::VorbisComments(ref tag) => tag.get("COMPILATION") == Some("1"), - }; - - let bpm = match file.metadata { - Metadata::Id3(ref tag) => get_frame_text(&tag, "TBPM").unwrap_or("").to_string(), - Metadata::Mp4(ref tag) => opt_to_str(tag.bpm()), - Metadata::VorbisComments(ref tag) => tag.get("BPM").unwrap_or("").to_string(), - }; - - #[derive(Serialize)] - struct Comment { - text: String, - lang: Option, - description: Option, - } - let comments: Vec = match file.metadata { - Metadata::Id3(ref tag) => tag - .comments() - .map(|c| Comment { - text: c.text.clone(), - lang: Some(c.lang.clone()), - description: Some(c.description.clone()), - }) - .collect(), - Metadata::Mp4(ref tag) => tag - .comments() - .map(|c| Comment { - text: c.to_string(), - lang: None, - description: None, - }) - .collect(), - Metadata::VorbisComments(ref tag) => tag - .get_all("COMMENT") - .map(|c| Comment { - text: c.to_string(), - lang: None, - description: None, - }) - .collect(), - }; - - Some(serde_json::json!({ - "path": file.path.clone(), - "title": title, - "artists": artists, - "album": album, - "album_artists": album_artists, - "composer": composer, - "groupings": groupings, - "genres": genres, - // year - "track_num": track_num, - "track_total": track_total, - "disc_num": disc_num, - "disc_total": disc_total, - "compilation": compilation, - "bpm": bpm, - "comments": comments, - "frames": file.metadata.get_frames(), - })) + let mut app = app.0.lock().unwrap(); + let file = app.current_file().ok()?; + + let title = match file.metadata { + Metadata::Id3(ref tag) => tag.title().unwrap_or("").to_string(), + Metadata::Mp4(ref tag) => tag.title().unwrap_or("").to_string(), + Metadata::VorbisComments(ref tag) => tag.title().map(|s| s.to_string()).unwrap_or_default(), + }; + + let artists: Vec<_> = match file.metadata { + Metadata::Id3(ref tag) => id3_split(tag.artist()), + Metadata::Mp4(ref tag) => tag.artists().collect(), + Metadata::VorbisComments(ref tag) => tag.get_all("ARTIST").collect(), + }; + + let album = match file.metadata { + Metadata::Id3(ref tag) => tag.album().unwrap_or("").to_string(), + Metadata::Mp4(ref tag) => tag.album().unwrap_or("").to_string(), + Metadata::VorbisComments(ref tag) => tag.album().map(|s| s.to_string()).unwrap_or_default(), + }; + + let album_artists: Vec<_> = match file.metadata { + Metadata::Id3(ref tag) => id3_split(tag.album_artist()), + Metadata::Mp4(ref tag) => tag.album_artists().collect(), + Metadata::VorbisComments(ref tag) => tag.get_all("ALBUMARTIST").collect(), + }; + + let composer = match file.metadata { + Metadata::Id3(ref tag) => id3_split(get_frame_text(&tag, "TCOM")), + Metadata::Mp4(ref tag) => tag.composers().collect(), + Metadata::VorbisComments(ref tag) => tag.get_all("COMPOSER").collect(), + }; + + let groupings = match file.metadata { + // non-standard iTunes tag, three-byte ID3v2.2 tag is not remapped + Metadata::Id3(ref tag) => id3_split(match get_frame_text(&tag, "GRP1") { + Some(s) => Some(s), + None => get_frame_text(&tag, "GP1"), + }), + Metadata::Mp4(ref tag) => tag.groupings().collect(), + Metadata::VorbisComments(ref tag) => tag.get_all("GROUPING").collect(), + }; + + let genres = match file.metadata { + Metadata::Id3(ref tag) => id3_split(tag.genre()), + Metadata::Mp4(ref tag) => tag.genres().collect(), + Metadata::VorbisComments(ref tag) => tag.get_all("GENRE").collect(), + }; + + let track_num = match file.metadata { + Metadata::Id3(ref tag) => opt_to_str(tag.track()), + Metadata::Mp4(ref tag) => opt_to_str(tag.track().0), + Metadata::VorbisComments(ref tag) => opt_to_str(tag.track()), + }; + let track_total = match file.metadata { + Metadata::Id3(ref tag) => opt_to_str(tag.total_tracks()), + Metadata::Mp4(ref tag) => opt_to_str(tag.track().1), + Metadata::VorbisComments(ref tag) => opt_to_str(tag.track_total()), + }; + + let disc_num = match file.metadata { + Metadata::Id3(ref tag) => opt_to_str(tag.disc()), + Metadata::Mp4(ref tag) => opt_to_str(tag.disc().0), + Metadata::VorbisComments(ref tag) => opt_to_str(tag.disk()), + }; + let disc_total = match file.metadata { + Metadata::Id3(ref tag) => opt_to_str(tag.total_discs()), + Metadata::Mp4(ref tag) => opt_to_str(tag.disc().1), + Metadata::VorbisComments(ref tag) => opt_to_str(tag.disk_total()), + }; + + let compilation = match file.metadata { + Metadata::Id3(ref tag) => { + // non-standard iTunes tag, three-byte ID3v2.2 tag is not remapped + get_frame_text(&tag, "TCMP") == Some("1") || get_frame_text(&tag, "TCP") == Some("1") + } + Metadata::Mp4(ref tag) => tag.compilation(), + Metadata::VorbisComments(ref tag) => tag.get("COMPILATION") == Some("1"), + }; + + let bpm = match file.metadata { + Metadata::Id3(ref tag) => get_frame_text(&tag, "TBPM").unwrap_or("").to_string(), + Metadata::Mp4(ref tag) => opt_to_str(tag.bpm()), + Metadata::VorbisComments(ref tag) => tag.get("BPM").unwrap_or("").to_string(), + }; + + #[derive(Serialize)] + struct Comment { + text: String, + lang: Option, + description: Option, + } + let comments: Vec = match file.metadata { + Metadata::Id3(ref tag) => tag + .comments() + .map(|c| Comment { + text: c.text.clone(), + lang: Some(c.lang.clone()), + description: Some(c.description.clone()), + }) + .collect(), + Metadata::Mp4(ref tag) => tag + .comments() + .map(|c| Comment { + text: c.to_string(), + lang: None, + description: None, + }) + .collect(), + Metadata::VorbisComments(ref tag) => tag + .get_all("COMMENT") + .map(|c| Comment { + text: c.to_string(), + lang: None, + description: None, + }) + .collect(), + }; + + Some(serde_json::json!({ + "path": file.path.clone(), + "title": title, + "artists": artists, + "album": album, + "album_artists": album_artists, + "composer": composer, + "groupings": groupings, + "genres": genres, + // year + "track_num": track_num, + "track_total": track_total, + "disc_num": disc_num, + "disc_total": disc_total, + "compilation": compilation, + "bpm": bpm, + "comments": comments, + "frames": file.metadata.get_frames(), + })) } diff --git a/src-tauri/src/files.rs b/src-tauri/src/files.rs index 934a434..bb02508 100644 --- a/src-tauri/src/files.rs +++ b/src-tauri/src/files.rs @@ -8,122 +8,122 @@ use tauri::api::dialog; use tauri::command; fn open_file(path: &PathBuf) -> Result { - match fs::File::open(&path) { - Ok(f) => Ok(f), - Err(e) => throw!("Error opening file {}: {}", path.to_string_lossy(), e), - } + match fs::File::open(&path) { + Ok(f) => Ok(f), + Err(e) => throw!("Error opening file {}: {}", path.to_string_lossy(), e), + } } fn get_metadata(path: &PathBuf) -> Result { - let ext = path.extension().unwrap_or_default().to_string_lossy(); - let path_str = path.to_string_lossy(); - let metadata = match ext.as_ref() { - // ID3 - "mp3" | "aiff" => { - let tag = match id3::Tag::read_from_path(&path) { - Ok(tag) => tag, - Err(e) => match e.kind { - id3::ErrorKind::NoTag => id3::Tag::default(), - _ => throw!("Error reading tag for file {}: {}", path_str, e.description), - }, - }; - Metadata::Id3(tag) - } - // iTunes-style - "m4a" | "mp4" | "m4p" | "m4b" | "m4r" | "m4v" => { - let tag = match mp4ameta::Tag::read_from_path(&path) { - Ok(tag) => tag, - Err(e) => match e.kind { - mp4ameta::ErrorKind::NoTag => mp4ameta::Tag::default(), - _ => throw!("Error reading tag for file {}: {}", path_str, e.description), - }, - }; - Metadata::Mp4(tag) - } - "opus" => { - let mut file = open_file(&path)?; - let mut opus = match lofty::ogg::OpusFile::read_from( - &mut file, - lofty::ParseOptions::new().read_properties(false), - ) { - Ok(f) => f, - Err(e) => throw!("Error reading tag for file {}: {}", path_str, e), - }; - Metadata::VorbisComments(opus.remove_vorbis_comments()) - } - _ => throw!("Unsupported file type"), - }; - Ok(metadata) + let ext = path.extension().unwrap_or_default().to_string_lossy(); + let path_str = path.to_string_lossy(); + let metadata = match ext.as_ref() { + // ID3 + "mp3" | "aiff" => { + let tag = match id3::Tag::read_from_path(&path) { + Ok(tag) => tag, + Err(e) => match e.kind { + id3::ErrorKind::NoTag => id3::Tag::default(), + _ => throw!("Error reading tag for file {}: {}", path_str, e.description), + }, + }; + Metadata::Id3(tag) + } + // iTunes-style + "m4a" | "mp4" | "m4p" | "m4b" | "m4r" | "m4v" => { + let tag = match mp4ameta::Tag::read_from_path(&path) { + Ok(tag) => tag, + Err(e) => match e.kind { + mp4ameta::ErrorKind::NoTag => mp4ameta::Tag::default(), + _ => throw!("Error reading tag for file {}: {}", path_str, e.description), + }, + }; + Metadata::Mp4(tag) + } + "opus" => { + let mut file = open_file(&path)?; + let mut opus = match lofty::ogg::OpusFile::read_from( + &mut file, + lofty::ParseOptions::new().read_properties(false), + ) { + Ok(f) => f, + Err(e) => throw!("Error reading tag for file {}: {}", path_str, e), + }; + Metadata::VorbisComments(opus.remove_vorbis_comments()) + } + _ => throw!("Unsupported file type"), + }; + Ok(metadata) } #[command] pub async fn open_files(paths: Vec, app: AppArg<'_>) -> Result<(), String> { - let mut app = app.0.lock().unwrap(); - let initial_len = app.files.len(); - for path in paths { - let is_duplicate = app.files.iter().any(|f| f.path == path); - if !is_duplicate { - let metadata = get_metadata(&path)?; - app.files.push(File { - path: path.clone(), - dirty: false, - metadata: metadata.clone(), - }); - } - } - if initial_len == 0 && app.files.len() >= 1 { - app.current_index = app.files.len() - 1; - } - Ok(()) + let mut app = app.0.lock().unwrap(); + let initial_len = app.files.len(); + for path in paths { + let is_duplicate = app.files.iter().any(|f| f.path == path); + if !is_duplicate { + let metadata = get_metadata(&path)?; + app.files.push(File { + path: path.clone(), + dirty: false, + metadata: metadata.clone(), + }); + } + } + if initial_len == 0 && app.files.len() >= 1 { + app.current_index = app.files.len() - 1; + } + Ok(()) } #[command] pub async fn close_file(index: usize, app: AppArg<'_>) -> Result<(), String> { - let mut app = app.0.lock().unwrap(); - app.files.remove(index); - if app.current_index >= index && index >= 1 { - app.current_index -= 1; - } - Ok(()) + let mut app = app.0.lock().unwrap(); + app.files.remove(index); + if app.current_index >= index && index >= 1 { + app.current_index -= 1; + } + Ok(()) } #[command] pub async fn save_file(index: usize, save_as: bool, app: AppArg<'_>) -> Result<(), String> { - let mut app = app.0.lock().unwrap(); - let mut file = app.files.get_mut(index).unwrap(); - if save_as { - let ext = file.path.extension().unwrap_or_default().to_string_lossy(); - let (sender, receiver) = std::sync::mpsc::channel(); - dialog::FileDialogBuilder::new() - .set_file_name("report.kryp") - .add_filter("Audio/Video file", &[&ext]) - .save_file(move |p| { - sender.send(p).unwrap(); - }); - let new_path = match receiver.recv().unwrap_or_default() { - Some(file_path) => file_path, - None => return Ok(()), - }; - match fs::copy(&file.path, &new_path) { - Ok(_) => {} - Err(e) => throw!("Error copying file: {}", e), - } - file.path = new_path; - } - match file.metadata { - Metadata::Id3(ref tag) => match tag.write_to_path(&file.path, id3::Version::Id3v24) { - Ok(_) => {} - Err(e) => throw!("Error saving file: {}", e.description), - }, - Metadata::Mp4(ref tag) => match tag.write_to_path(&file.path) { - Ok(_) => {} - Err(e) => throw!("Error saving file: {}", e.description), - }, - Metadata::VorbisComments(ref tag) => match tag.save_to_path(&file.path) { - Ok(_) => {} - Err(e) => throw!("Error saving file: {}", e.to_string()), - }, - } - file.dirty = false; - Ok(()) + let mut app = app.0.lock().unwrap(); + let mut file = app.files.get_mut(index).unwrap(); + if save_as { + let ext = file.path.extension().unwrap_or_default().to_string_lossy(); + let (sender, receiver) = std::sync::mpsc::channel(); + dialog::FileDialogBuilder::new() + .set_file_name("report.kryp") + .add_filter("Audio/Video file", &[&ext]) + .save_file(move |p| { + sender.send(p).unwrap(); + }); + let new_path = match receiver.recv().unwrap_or_default() { + Some(file_path) => file_path, + None => return Ok(()), + }; + match fs::copy(&file.path, &new_path) { + Ok(_) => {} + Err(e) => throw!("Error copying file: {}", e), + } + file.path = new_path; + } + match file.metadata { + Metadata::Id3(ref tag) => match tag.write_to_path(&file.path, id3::Version::Id3v24) { + Ok(_) => {} + Err(e) => throw!("Error saving file: {}", e.description), + }, + Metadata::Mp4(ref tag) => match tag.write_to_path(&file.path) { + Ok(_) => {} + Err(e) => throw!("Error saving file: {}", e.description), + }, + Metadata::VorbisComments(ref tag) => match tag.save_to_path(&file.path) { + Ok(_) => {} + Err(e) => throw!("Error saving file: {}", e.to_string()), + }, + } + file.dirty = false; + Ok(()) } diff --git a/src-tauri/src/frames.rs b/src-tauri/src/frames.rs index 3d9e725..73e4581 100644 --- a/src-tauri/src/frames.rs +++ b/src-tauri/src/frames.rs @@ -3,100 +3,100 @@ use serde::Serialize; #[derive(Clone)] pub enum Metadata { - Id3(id3::Tag), - Mp4(mp4ameta::Tag), - VorbisComments(lofty::ogg::VorbisComments), + Id3(id3::Tag), + Mp4(mp4ameta::Tag), + VorbisComments(lofty::ogg::VorbisComments), } impl Metadata { - pub fn get_frames(&self) -> Vec { - match self { - Self::Id3(id3) => get_id3_frames(id3), - Self::Mp4(mp4) => get_mp4_frames(mp4), - Self::VorbisComments(vorbis_comments) => get_vorbis_comments_frames(vorbis_comments), - } - } + pub fn get_frames(&self) -> Vec { + match self { + Self::Id3(id3) => get_id3_frames(id3), + Self::Mp4(mp4) => get_mp4_frames(mp4), + Self::VorbisComments(vorbis_comments) => get_vorbis_comments_frames(vorbis_comments), + } + } } #[derive(Serialize)] pub enum Frame { - Text { id: String, value: String }, + Text { id: String, value: String }, } fn get_id3_frames(tag: &id3::Tag) -> Vec { - let mut frames = Vec::new(); - for frame in tag.frames() { - match frame.content() { - id3::Content::Text(s) => { - frames.push(Frame::Text { - id: frame.id().to_string(), - value: s.to_string(), - }); - } - // !TODO - id3::Content::ExtendedText(_) => {} - // !TODO - id3::Content::Link(_) => {} - // !TODO - id3::Content::ExtendedLink(_) => {} - // !TODO - id3::Content::Comment(_) => {} - // !TODO - id3::Content::Lyrics(_) => {} - // !TODO - id3::Content::SynchronisedLyrics(_) => {} - // !TODO - id3::Content::Picture(_) => {} - // !TODO - id3::Content::EncapsulatedObject(_) => {} - // !TODO - id3::Content::Unknown(_) => {} - // !TODO - id3::Content::Popularimeter(_) => {} - // !TODO - id3::Content::Chapter(_) => {} - // !TODO - id3::Content::MpegLocationLookupTable(_) => {} + let mut frames = Vec::new(); + for frame in tag.frames() { + match frame.content() { + id3::Content::Text(s) => { + frames.push(Frame::Text { + id: frame.id().to_string(), + value: s.to_string(), + }); + } + // !TODO + id3::Content::ExtendedText(_) => {} + // !TODO + id3::Content::Link(_) => {} + // !TODO + id3::Content::ExtendedLink(_) => {} + // !TODO + id3::Content::Comment(_) => {} + // !TODO + id3::Content::Lyrics(_) => {} + // !TODO + id3::Content::SynchronisedLyrics(_) => {} + // !TODO + id3::Content::Picture(_) => {} + // !TODO + id3::Content::EncapsulatedObject(_) => {} + // !TODO + id3::Content::Unknown(_) => {} + // !TODO + id3::Content::Popularimeter(_) => {} + // !TODO + id3::Content::Chapter(_) => {} + // !TODO + id3::Content::MpegLocationLookupTable(_) => {} - _ => {} - } - } - frames + _ => {} + } + } + frames } fn get_mp4_frames(tag: &mp4ameta::Tag) -> Vec { - let mut frames = Vec::new(); - for (id, data) in tag.data() { - match data { - // !TODO - mp4ameta::Data::Reserved(_) => {} - mp4ameta::Data::Utf8(s) => { - frames.push(Frame::Text { - id: id.to_string(), - value: s.to_string(), - }); - } - // !TODO - mp4ameta::Data::Utf16(_) => {} - // !TODO - mp4ameta::Data::Jpeg(_) => {} - // !TODO - mp4ameta::Data::Png(_) => {} - // !TODO - mp4ameta::Data::BeSigned(_) => {} - // !TODO - mp4ameta::Data::Bmp(_) => {} - } - } - frames + let mut frames = Vec::new(); + for (id, data) in tag.data() { + match data { + // !TODO + mp4ameta::Data::Reserved(_) => {} + mp4ameta::Data::Utf8(s) => { + frames.push(Frame::Text { + id: id.to_string(), + value: s.to_string(), + }); + } + // !TODO + mp4ameta::Data::Utf16(_) => {} + // !TODO + mp4ameta::Data::Jpeg(_) => {} + // !TODO + mp4ameta::Data::Png(_) => {} + // !TODO + mp4ameta::Data::BeSigned(_) => {} + // !TODO + mp4ameta::Data::Bmp(_) => {} + } + } + frames } fn get_vorbis_comments_frames(tag: &VorbisComments) -> Vec { - let mut frames = Vec::new(); - for (key, value) in tag.items() { - frames.push(Frame::Text { - id: key.to_string(), - value: value.to_string(), - }); - } - frames + let mut frames = Vec::new(); + for (key, value) in tag.items() { + frames.push(Frame::Text { + id: key.to_string(), + value: value.to_string(), + }); + } + frames } diff --git a/src-tauri/src/image.rs b/src-tauri/src/image.rs index 7a60312..4bf9340 100644 --- a/src-tauri/src/image.rs +++ b/src-tauri/src/image.rs @@ -11,260 +11,262 @@ use tauri::command; #[derive(Serialize)] pub struct Image { - index: usize, - total_images: usize, - data: String, - mime_type: String, - description: Option, - picture_type: Option, + index: usize, + total_images: usize, + data: String, + mime_type: String, + description: Option, + picture_type: Option, } #[command] pub fn get_image(index: Option, app: AppArg<'_>) -> Result, String> { - let mut app = app.0.lock().unwrap(); - let file = app.current_file()?; - let index = match index { - Some(index) => index, - None => match file.metadata { - Metadata::Id3(ref tag) => { - let mut index = match tag.pictures().next() { - Some(_pic) => 0, - None => return Ok(None), - }; - for (i, current_pic) in tag.pictures().enumerate() { - if current_pic.picture_type == id3::frame::PictureType::CoverFront { - index = i; - break; - } - } - index - } - Metadata::Mp4(ref tag) => match tag.artwork() { - Some(_artwork) => 0, - None => return Ok(None), - }, - Metadata::VorbisComments(ref tag) => { - if tag.pictures().len() == 0 { - return Ok(None); - } - 0 - } - }, - }; - let image_option = match file.metadata { - Metadata::Id3(ref tag) => match tag.pictures().nth(index) { - Some(pic) => Some(Image { - index, - total_images: tag.pictures().count(), - data: base64::encode(&pic.data), - mime_type: pic.mime_type.clone(), - description: Some(pic.description.clone()), - picture_type: Some(pic.picture_type.to_string()), - }), - None => None, - }, - Metadata::Mp4(ref tag) => match tag.artworks().nth(index) { - Some(artwork) => Some(Image { - index, - total_images: tag.artworks().count(), - data: base64::encode(&artwork.data), - mime_type: match artwork.fmt { - mp4ameta::ImgFmt::Bmp => "image/bmp".to_string(), - mp4ameta::ImgFmt::Jpeg => "image/jpeg".to_string(), - mp4ameta::ImgFmt::Png => "image/png".to_string(), - }, - description: None, - picture_type: None, - }), - None => None, - }, - Metadata::VorbisComments(ref tag) => match tag.pictures().get(index) { - Some((pic, _info)) => Some(Image { - index, - total_images: tag.pictures().len(), - data: base64::encode(pic.data()), - mime_type: match pic.mime_type() { - lofty::MimeType::Png => "image/png".to_string(), - lofty::MimeType::Jpeg => "image/jpeg".to_string(), - lofty::MimeType::Tiff => "image/tiff".to_string(), - lofty::MimeType::Bmp => "image/bmp".to_string(), - lofty::MimeType::Gif => "image/gif".to_string(), - lofty::MimeType::Unknown(unknown) => throw!("Unknown picture type {unknown}"), - lofty::MimeType::None => throw!("No picture type"), - _ => throw!("Unsupported picture type"), - }, - description: pic.description().map(|s| s.to_string()), - picture_type: Some(match pic.pic_type() { - lofty::PictureType::Other => "Other".to_string(), - lofty::PictureType::Icon => "Icon".to_string(), - lofty::PictureType::OtherIcon => "Other icon".to_string(), - lofty::PictureType::CoverFront => "Front cover".to_string(), - lofty::PictureType::CoverBack => "Back cover".to_string(), - lofty::PictureType::Leaflet => "Leaflet".to_string(), - lofty::PictureType::Media => "Media".to_string(), - lofty::PictureType::LeadArtist => "Lead artist".to_string(), - lofty::PictureType::Artist => "Artist".to_string(), - lofty::PictureType::Conductor => "Conductor".to_string(), - lofty::PictureType::Band => "Band".to_string(), - lofty::PictureType::Composer => "Composer".to_string(), - lofty::PictureType::Lyricist => "Lyricist".to_string(), - lofty::PictureType::RecordingLocation => "Recording location".to_string(), - lofty::PictureType::DuringRecording => "During recording".to_string(), - lofty::PictureType::DuringPerformance => "During performance".to_string(), - lofty::PictureType::ScreenCapture => "Screen capture".to_string(), - lofty::PictureType::BrightFish => "Bright fish".to_string(), - lofty::PictureType::Illustration => "Illustration".to_string(), - lofty::PictureType::BandLogo => "Band logo".to_string(), - lofty::PictureType::PublisherLogo => "Publisher logo".to_string(), - lofty::PictureType::Undefined(u) => throw!("Undefined type {u}"), - _ => throw!("Unsupported picture type {:?}", pic.pic_type()), - }), - }), - None => None, - }, - }; - Ok(image_option) + let mut app = app.0.lock().unwrap(); + let file = app.current_file()?; + let index = match index { + Some(index) => index, + None => match file.metadata { + Metadata::Id3(ref tag) => { + let mut index = match tag.pictures().next() { + Some(_pic) => 0, + None => return Ok(None), + }; + for (i, current_pic) in tag.pictures().enumerate() { + if current_pic.picture_type == id3::frame::PictureType::CoverFront { + index = i; + break; + } + } + index + } + Metadata::Mp4(ref tag) => match tag.artwork() { + Some(_artwork) => 0, + None => return Ok(None), + }, + Metadata::VorbisComments(ref tag) => { + if tag.pictures().len() == 0 { + return Ok(None); + } + 0 + } + }, + }; + let image_option = match file.metadata { + Metadata::Id3(ref tag) => match tag.pictures().nth(index) { + Some(pic) => Some(Image { + index, + total_images: tag.pictures().count(), + data: base64::encode(&pic.data), + mime_type: pic.mime_type.clone(), + description: Some(pic.description.clone()), + picture_type: Some(pic.picture_type.to_string()), + }), + None => None, + }, + Metadata::Mp4(ref tag) => match tag.artworks().nth(index) { + Some(artwork) => Some(Image { + index, + total_images: tag.artworks().count(), + data: base64::encode(&artwork.data), + mime_type: match artwork.fmt { + mp4ameta::ImgFmt::Bmp => "image/bmp".to_string(), + mp4ameta::ImgFmt::Jpeg => "image/jpeg".to_string(), + mp4ameta::ImgFmt::Png => "image/png".to_string(), + }, + description: None, + picture_type: None, + }), + None => None, + }, + Metadata::VorbisComments(ref tag) => match tag.pictures().get(index) { + Some((pic, _info)) => Some(Image { + index, + total_images: tag.pictures().len(), + data: base64::encode(pic.data()), + mime_type: match pic.mime_type() { + lofty::MimeType::Png => "image/png".to_string(), + lofty::MimeType::Jpeg => "image/jpeg".to_string(), + lofty::MimeType::Tiff => "image/tiff".to_string(), + lofty::MimeType::Bmp => "image/bmp".to_string(), + lofty::MimeType::Gif => "image/gif".to_string(), + lofty::MimeType::Unknown(unknown) => throw!("Unknown picture type {unknown}"), + lofty::MimeType::None => throw!("No picture type"), + _ => throw!("Unsupported picture type"), + }, + description: pic.description().map(|s| s.to_string()), + picture_type: Some(match pic.pic_type() { + lofty::PictureType::Other => "Other".to_string(), + lofty::PictureType::Icon => "Icon".to_string(), + lofty::PictureType::OtherIcon => "Other icon".to_string(), + lofty::PictureType::CoverFront => "Front cover".to_string(), + lofty::PictureType::CoverBack => "Back cover".to_string(), + lofty::PictureType::Leaflet => "Leaflet".to_string(), + lofty::PictureType::Media => "Media".to_string(), + lofty::PictureType::LeadArtist => "Lead artist".to_string(), + lofty::PictureType::Artist => "Artist".to_string(), + lofty::PictureType::Conductor => "Conductor".to_string(), + lofty::PictureType::Band => "Band".to_string(), + lofty::PictureType::Composer => "Composer".to_string(), + lofty::PictureType::Lyricist => "Lyricist".to_string(), + lofty::PictureType::RecordingLocation => "Recording location".to_string(), + lofty::PictureType::DuringRecording => "During recording".to_string(), + lofty::PictureType::DuringPerformance => "During performance".to_string(), + lofty::PictureType::ScreenCapture => "Screen capture".to_string(), + lofty::PictureType::BrightFish => "Bright fish".to_string(), + lofty::PictureType::Illustration => "Illustration".to_string(), + lofty::PictureType::BandLogo => "Band logo".to_string(), + lofty::PictureType::PublisherLogo => "Publisher logo".to_string(), + lofty::PictureType::Undefined(u) => throw!("Undefined type {u}"), + _ => throw!("Unsupported picture type {:?}", pic.pic_type()), + }), + }), + None => None, + }, + }; + Ok(image_option) } #[command] pub fn remove_image(index: usize, app: AppArg<'_>) -> Result<(), String> { - let mut app = app.0.lock().unwrap(); - let file = app.current_file().unwrap(); - match file.metadata { - Metadata::Id3(ref mut tag) => { - let mut pic_frames: Vec<_> = tag - .frames() - .filter(|frame| frame.content().picture().is_some()) - .map(|frame| frame.clone()) - .collect(); - pic_frames.remove(index); - tag.remove_all_pictures(); - for pic_frame in pic_frames { - tag.add_frame(pic_frame); - } - } - Metadata::Mp4(ref mut tag) => { - let mut artworks: Vec<_> = tag.take_artworks().collect(); - artworks.remove(index); - tag.set_artworks(artworks); - } - Metadata::VorbisComments(ref mut tag) => { - tag.remove_picture(index); - } - } - file.dirty = true; - Ok(()) + let mut app = app.0.lock().unwrap(); + let file = app.current_file().unwrap(); + match file.metadata { + Metadata::Id3(ref mut tag) => { + let mut pic_frames: Vec<_> = tag + .frames() + .filter(|frame| frame.content().picture().is_some()) + .map(|frame| frame.clone()) + .collect(); + pic_frames.remove(index); + tag.remove_all_pictures(); + for pic_frame in pic_frames { + tag.add_frame(pic_frame); + } + } + Metadata::Mp4(ref mut tag) => { + let mut artworks: Vec<_> = tag.take_artworks().collect(); + artworks.remove(index); + tag.set_artworks(artworks); + } + Metadata::VorbisComments(ref mut tag) => { + tag.remove_picture(index); + } + } + file.dirty = true; + Ok(()) } enum MimeType { - Png, - Jpeg, + Png, + Jpeg, } #[command] pub fn set_image(index: usize, path: PathBuf, app: AppArg<'_>) -> Result<(), String> { - let mut app = app.0.lock().unwrap(); - let file = app.current_file().unwrap(); - let new_bytes = match fs::read(&path) { - Ok(b) => b, - Err(e) => throw!("Error reading that file: {}", e), - }; - let ext = path.extension().unwrap_or_default().to_string_lossy(); - let mime_type = match ext.as_ref() { - "jpg" | "jpeg" => MimeType::Jpeg, - "png" => MimeType::Png, - ext => throw!("Unsupported file type: {}", ext), - }; - match file.metadata { - Metadata::Id3(ref mut tag) => { - let mut pic_frames: Vec<_> = tag - .frames() - .filter(|frame| frame.content().picture().is_some()) - .map(|frame| frame.clone()) - .collect(); - let mime_type = match ext.as_ref() { - "jpg" | "jpeg" => "image/jpeg".to_string(), - "png" => "image/png".to_string(), - ext => throw!("Unsupported file type: {}", ext), - }; - let mut new_pic = id3::frame::Picture { - mime_type, - picture_type: id3::frame::PictureType::Other, - description: "".to_string(), - data: new_bytes, - }; - match pic_frames.get_mut(index) { - Some(old_frame) => { - let old_pic = old_frame.content().picture().unwrap(); - new_pic.picture_type = old_pic.picture_type; - new_pic.description = old_pic.description.clone(); - let new_frame = id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); - *old_frame = new_frame; - } - None => { - if index == pic_frames.len() { - let new_frame = id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); - pic_frames.insert(index, new_frame); - } else { - throw!("Index out of range"); - } - } - } - tag.remove_all_pictures(); - for pic_frame in pic_frames { - tag.add_frame(pic_frame); - } - } - Metadata::Mp4(ref mut tag) => { - let mut artworks: Vec<_> = tag.take_artworks().collect(); - let new_artwork = mp4ameta::Img { - fmt: match ext.as_ref() { - "jpg" | "jpeg" => mp4ameta::ImgFmt::Jpeg, - "png" => mp4ameta::ImgFmt::Png, - "bmp" => mp4ameta::ImgFmt::Bmp, - ext => throw!("Unsupported file type: {}", ext), - }, - data: new_bytes, - }; - match artworks.get_mut(index) { - Some(artwork) => { - *artwork = new_artwork; - } - None => { - if index == artworks.len() { - artworks.push(new_artwork); - } else { - throw!("Index out of range"); - } - } - } - tag.set_artworks(artworks); - } - Metadata::VorbisComments(ref mut tag) => { - if index <= tag.pictures().len() { - let info_result = match mime_type { - MimeType::Png => lofty::PictureInformation::from_png(&new_bytes), - MimeType::Jpeg => lofty::PictureInformation::from_jpeg(&new_bytes), - }; - let info = match info_result { - Ok(info) => info, - Err(e) => throw!("Error reading picture info: {}", e), - }; - let mut byte_cursor = std::io::Cursor::new(new_bytes); - let pic = match lofty::Picture::from_reader(&mut byte_cursor) { - Ok(mut pic) => { - pic.set_pic_type(lofty::PictureType::Other); - pic - } - Err(e) => throw!("Error reading picture: {}", e), - }; - // this is safe because set_picture appends if out of bounds: - tag.set_picture(index, pic, info); - } else { - throw!("Index out of range"); - } - } - } - file.dirty = true; - Ok(()) + let mut app = app.0.lock().unwrap(); + let file = app.current_file().unwrap(); + let new_bytes = match fs::read(&path) { + Ok(b) => b, + Err(e) => throw!("Error reading that file: {}", e), + }; + let ext = path.extension().unwrap_or_default().to_string_lossy(); + let mime_type = match ext.as_ref() { + "jpg" | "jpeg" => MimeType::Jpeg, + "png" => MimeType::Png, + ext => throw!("Unsupported file type: {}", ext), + }; + match file.metadata { + Metadata::Id3(ref mut tag) => { + let mut pic_frames: Vec<_> = tag + .frames() + .filter(|frame| frame.content().picture().is_some()) + .map(|frame| frame.clone()) + .collect(); + let mime_type = match ext.as_ref() { + "jpg" | "jpeg" => "image/jpeg".to_string(), + "png" => "image/png".to_string(), + ext => throw!("Unsupported file type: {}", ext), + }; + let mut new_pic = id3::frame::Picture { + mime_type, + picture_type: id3::frame::PictureType::Other, + description: "".to_string(), + data: new_bytes, + }; + match pic_frames.get_mut(index) { + Some(old_frame) => { + let old_pic = old_frame.content().picture().unwrap(); + new_pic.picture_type = old_pic.picture_type; + new_pic.description = old_pic.description.clone(); + let new_frame = + id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); + *old_frame = new_frame; + } + None => { + if index == pic_frames.len() { + let new_frame = + id3::Frame::with_content("APIC", id3::Content::Picture(new_pic)); + pic_frames.insert(index, new_frame); + } else { + throw!("Index out of range"); + } + } + } + tag.remove_all_pictures(); + for pic_frame in pic_frames { + tag.add_frame(pic_frame); + } + } + Metadata::Mp4(ref mut tag) => { + let mut artworks: Vec<_> = tag.take_artworks().collect(); + let new_artwork = mp4ameta::Img { + fmt: match ext.as_ref() { + "jpg" | "jpeg" => mp4ameta::ImgFmt::Jpeg, + "png" => mp4ameta::ImgFmt::Png, + "bmp" => mp4ameta::ImgFmt::Bmp, + ext => throw!("Unsupported file type: {}", ext), + }, + data: new_bytes, + }; + match artworks.get_mut(index) { + Some(artwork) => { + *artwork = new_artwork; + } + None => { + if index == artworks.len() { + artworks.push(new_artwork); + } else { + throw!("Index out of range"); + } + } + } + tag.set_artworks(artworks); + } + Metadata::VorbisComments(ref mut tag) => { + if index <= tag.pictures().len() { + let info_result = match mime_type { + MimeType::Png => lofty::PictureInformation::from_png(&new_bytes), + MimeType::Jpeg => lofty::PictureInformation::from_jpeg(&new_bytes), + }; + let info = match info_result { + Ok(info) => info, + Err(e) => throw!("Error reading picture info: {}", e), + }; + let mut byte_cursor = std::io::Cursor::new(new_bytes); + let pic = match lofty::Picture::from_reader(&mut byte_cursor) { + Ok(mut pic) => { + pic.set_pic_type(lofty::PictureType::Other); + pic + } + Err(e) => throw!("Error reading picture: {}", e), + }; + // this is safe because set_picture appends if out of bounds: + tag.set_picture(index, pic, info); + } else { + throw!("Index out of range"); + } + } + } + file.dirty = true; + Ok(()) } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index f777af7..065bf4b 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,14 +1,14 @@ #![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" )] use crate::cmd::AppArg; use std::thread; use tauri::api::{dialog, shell}; use tauri::{ - AboutMetadata, AppHandle, CustomMenuItem, Manager, Menu, MenuEntry, MenuItem, Submenu, - WindowBuilder, WindowUrl, + AboutMetadata, AppHandle, CustomMenuItem, Manager, Menu, MenuEntry, MenuItem, Submenu, + WindowBuilder, WindowUrl, }; mod cmd; @@ -24,149 +24,150 @@ macro_rules! throw { } fn main() { - let ctx = tauri::generate_context!(); - let app = tauri::Builder::default() - .invoke_handler(tauri::generate_handler![ - cmd::error_popup, - cmd::get_app, - cmd::show, - cmd::close_window, - cmd::get_page, - files::open_files, - files::close_file, - files::save_file, - image::get_image, - image::remove_image, - image::set_image, - ]) - .setup(|app| { - let _ = WindowBuilder::new(app, "main", WindowUrl::default()) - .title("Mr Tagger") - .resizable(true) - .decorations(true) - .always_on_top(false) - .inner_size(800.0, 550.0) - .min_inner_size(400.0, 200.0) - .skip_taskbar(false) - .build() - .expect("Unable to create window"); - Ok(()) - }) - .manage(cmd::AppState(Default::default())) - .menu(Menu::with_items([ - #[cfg(target_os = "macos")] - MenuEntry::Submenu(Submenu::new( - &ctx.package_info().name, - Menu::with_items([ - MenuItem::About(ctx.package_info().name.clone(), AboutMetadata::default()).into(), - MenuItem::Separator.into(), - MenuItem::Services.into(), - MenuItem::Separator.into(), - MenuItem::Hide.into(), - MenuItem::HideOthers.into(), - MenuItem::ShowAll.into(), - MenuItem::Separator.into(), - MenuItem::Quit.into(), - ]), - )), - MenuEntry::Submenu(Submenu::new( - "File", - Menu::with_items([ - CustomMenuItem::new("Open...", "Open...") - .accelerator("cmdOrControl+O") - .into(), - MenuItem::Separator.into(), - CustomMenuItem::new("Close", "Close") - .accelerator("cmdOrControl+W") - .into(), - CustomMenuItem::new("Save", "Save") - .accelerator("cmdOrControl+S") - .into(), - CustomMenuItem::new("Save As...", "Save As...") - .accelerator("shift+cmdOrControl+S") - .into(), - ]), - )), - MenuEntry::Submenu(Submenu::new( - "Edit", - Menu::with_items([ - MenuItem::Undo.into(), - MenuItem::Redo.into(), - MenuItem::Separator.into(), - MenuItem::Cut.into(), - MenuItem::Copy.into(), - MenuItem::Paste.into(), - #[cfg(not(target_os = "macos"))] - MenuItem::Separator.into(), - MenuItem::SelectAll.into(), - ]), - )), - MenuEntry::Submenu(Submenu::new( - "View", - Menu::with_items([MenuItem::EnterFullScreen.into()]), - )), - MenuEntry::Submenu(Submenu::new( - "Window", - Menu::with_items([MenuItem::Minimize.into(), MenuItem::Zoom.into()]), - )), - // You should always have a Help menu on macOS because it will automatically - // show a menu search field - MenuEntry::Submenu(Submenu::new( - "Help", - Menu::with_items([CustomMenuItem::new("Learn More", "Learn More").into()]), - )), - ])) - .on_menu_event(|event| { - let event_name = event.menu_item_id(); - event.window().emit("menu", event_name).unwrap(); - match event_name { - "Learn More" => { - let link = "https://github.com/probablykasper/mr-tagger".to_string(); - shell::open(&event.window().shell_scope(), link, None).unwrap(); - } - _ => {} - } - }) - .build(ctx) - .expect("error while running tauri app"); - app.run(|_app_handle, e| match e { - tauri::RunEvent::WindowEvent { label, event, .. } => match event { - tauri::WindowEvent::CloseRequested { api, .. } => { - if label == "main" && is_dirty(&_app_handle) { - api.prevent_close(); - handle_close_requested(_app_handle.clone()) - } - } - _ => {} - }, - _ => {} - }); + let ctx = tauri::generate_context!(); + let app = tauri::Builder::default() + .invoke_handler(tauri::generate_handler![ + cmd::error_popup, + cmd::get_app, + cmd::show, + cmd::close_window, + cmd::get_page, + files::open_files, + files::close_file, + files::save_file, + image::get_image, + image::remove_image, + image::set_image, + ]) + .setup(|app| { + let _ = WindowBuilder::new(app, "main", WindowUrl::default()) + .title("Mr Tagger") + .resizable(true) + .decorations(true) + .always_on_top(false) + .inner_size(800.0, 550.0) + .min_inner_size(400.0, 200.0) + .skip_taskbar(false) + .build() + .expect("Unable to create window"); + Ok(()) + }) + .manage(cmd::AppState(Default::default())) + .menu(Menu::with_items([ + #[cfg(target_os = "macos")] + MenuEntry::Submenu(Submenu::new( + &ctx.package_info().name, + Menu::with_items([ + MenuItem::About(ctx.package_info().name.clone(), AboutMetadata::default()) + .into(), + MenuItem::Separator.into(), + MenuItem::Services.into(), + MenuItem::Separator.into(), + MenuItem::Hide.into(), + MenuItem::HideOthers.into(), + MenuItem::ShowAll.into(), + MenuItem::Separator.into(), + MenuItem::Quit.into(), + ]), + )), + MenuEntry::Submenu(Submenu::new( + "File", + Menu::with_items([ + CustomMenuItem::new("Open...", "Open...") + .accelerator("cmdOrControl+O") + .into(), + MenuItem::Separator.into(), + CustomMenuItem::new("Close", "Close") + .accelerator("cmdOrControl+W") + .into(), + CustomMenuItem::new("Save", "Save") + .accelerator("cmdOrControl+S") + .into(), + CustomMenuItem::new("Save As...", "Save As...") + .accelerator("shift+cmdOrControl+S") + .into(), + ]), + )), + MenuEntry::Submenu(Submenu::new( + "Edit", + Menu::with_items([ + MenuItem::Undo.into(), + MenuItem::Redo.into(), + MenuItem::Separator.into(), + MenuItem::Cut.into(), + MenuItem::Copy.into(), + MenuItem::Paste.into(), + #[cfg(not(target_os = "macos"))] + MenuItem::Separator.into(), + MenuItem::SelectAll.into(), + ]), + )), + MenuEntry::Submenu(Submenu::new( + "View", + Menu::with_items([MenuItem::EnterFullScreen.into()]), + )), + MenuEntry::Submenu(Submenu::new( + "Window", + Menu::with_items([MenuItem::Minimize.into(), MenuItem::Zoom.into()]), + )), + // You should always have a Help menu on macOS because it will automatically + // show a menu search field + MenuEntry::Submenu(Submenu::new( + "Help", + Menu::with_items([CustomMenuItem::new("Learn More", "Learn More").into()]), + )), + ])) + .on_menu_event(|event| { + let event_name = event.menu_item_id(); + event.window().emit("menu", event_name).unwrap(); + match event_name { + "Learn More" => { + let link = "https://github.com/probablykasper/mr-tagger".to_string(); + shell::open(&event.window().shell_scope(), link, None).unwrap(); + } + _ => {} + } + }) + .build(ctx) + .expect("error while running tauri app"); + app.run(|_app_handle, e| match e { + tauri::RunEvent::WindowEvent { label, event, .. } => match event { + tauri::WindowEvent::CloseRequested { api, .. } => { + if label == "main" && is_dirty(&_app_handle) { + api.prevent_close(); + handle_close_requested(_app_handle.clone()) + } + } + _ => {} + }, + _ => {} + }); } fn is_dirty(app: &AppHandle) -> bool { - let app: AppArg<'_> = app.state(); - let app = app.0.lock().unwrap(); - for file in &app.files { - if file.dirty { - return true; - } - } - false + let app: AppArg<'_> = app.state(); + let app = app.0.lock().unwrap(); + for file in &app.files { + if file.dirty { + return true; + } + } + false } fn handle_close_requested(app_handle: AppHandle) { - thread::spawn(move || { - let w = app_handle.get_window("main").unwrap(); - dialog::ask( - Some(&w), - "You have unsaved changes. Close without saving?", - "", - move |res| { - let w = app_handle.get_window("main").unwrap(); - if res == true { - w.close().unwrap(); - } - }, - ); - }); + thread::spawn(move || { + let w = app_handle.get_window("main").unwrap(); + dialog::ask( + Some(&w), + "You have unsaved changes. Close without saving?", + "", + move |res| { + let w = app_handle.get_window("main").unwrap(); + if res == true { + w.close().unwrap(); + } + }, + ); + }); } diff --git a/src/App.svelte b/src/App.svelte index 66e81e3..3f0b257 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,161 +1,161 @@
-