Compare commits

...

10 Commits

Author SHA1 Message Date
712e918e08
We dont care about formatting THAT much
Some checks are pending
Continuous integration / Clippy (push) Waiting to run
Continuous integration / build (push) Waiting to run
2024-10-11 02:48:32 +03:00
787c01b9dc
More formatting
Some checks failed
Continuous integration / Clippy (push) Failing after 46s
Continuous integration / build (push) Successful in 1m0s
2024-10-11 02:46:27 +03:00
e0201ccb50
Some other formatting changes
Some checks failed
Continuous integration / Clippy (push) Failing after 42s
Continuous integration / build (push) Successful in 54s
2024-10-11 02:43:36 +03:00
7edb257e2f
Fixed clippy errors 2024-10-11 02:33:05 +03:00
dfc55837ee
Added rust-toolchain.toml to use nightly channel by default
Some checks failed
Continuous integration / Clippy (push) Failing after 3m9s
Continuous integration / build (push) Failing after 4m16s
2024-10-11 02:25:23 +03:00
0435d33e58
Remove rustfmt from CI
Some checks failed
Continuous integration / Clippy (push) Failing after 1m47s
Continuous integration / build (push) Has been cancelled
2024-10-11 02:20:32 +03:00
193029d4e1
Added initial CI
Some checks failed
Continuous integration / build (push) Waiting to run
Continuous integration / Rustfmt (push) Failing after 1m28s
Continuous integration / Clippy (push) Has been cancelled
2024-10-11 02:17:27 +03:00
14c53d96c0
Did #0 (added context menu to side_nav), fixed some spelling errors, implemented deleting from manifest for both side_nav and song_list 2024-10-10 22:42:04 +03:00
84ab965b9d
More todos! 2024-10-10 02:25:03 +03:00
2e57642aa3
Typo's, and fix up todo's 2024-10-10 02:20:33 +03:00
22 changed files with 410 additions and 160 deletions

37
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,37 @@
on: [push, pull_request]
name: Continuous integration
jobs:
#check:
# name: Check
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions-rust-lang/setup-rust-toolchain@v1
# - run: cargo check
#test:
# name: Test Suite
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions-rust-lang/setup-rust-toolchain@v1
# - run: cargo test
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: rustup component add clippy
- run: cargo clippy
build:
name: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo build

31
DEV.md
View File

@ -6,11 +6,11 @@ Todo types:
[BUG] \[loc\](/src/...) - Bugfix, mandatory location
[GIT] \[loc\](/src/...) - Git related feature, optional location
Fixed todos have to add `**DONE**` prefix to the type
Todos that have been merged have to add `**DONE**` prefix to the type
### #0
[FEAT] - [side_nav](/src/ui/gui/components/side_nav.rs)
Add dropdown menu for `side_nav` playlist
**DONE** ~~[FEAT] - [side_nav](/src/ui/gui/components/mod.rs)
Add dropdown menu for `side_nav` playlist~~
### #1
[FEAT] - [gui](/src/ui/gui/)
@ -25,7 +25,7 @@ Better styling
Add music player footer
### #4
[FEAT] - [gui](/src/ui/gui/components/song_list.rs)
[FEAT] - [gui](/src/ui/gui/components/song_list/mod.rs)
Add numbers to `song_list` table
### #5
@ -33,7 +33,7 @@ Add numbers to `song_list` table
Add music player logic
### #6
[FEAT] - [manifest](/src/manifest/)
[FEAT] - [manifest](/src/manifest/mod.rs)
Add support for images by possibly storing the images in json or custom format
### #7
@ -47,9 +47,10 @@ standalone one, moving default paths and using [#10](#10):
| music-output | `~/Music/mcmg/*` | `%userprofile%/Music/mcmg/*` |
### #8
[FEAT] - [cli](/src/ui/cli/)
[FEAT] - [cli](/src/ui/cli/mod.rs)
add missing commands that are available via gui
- Downloading single songs, from the manifest and standalone as an utility
- removing playlists, single songs
### #9
[BUG] - [utils](/src/util.rs)
@ -61,12 +62,24 @@ Add an utility to detect if this is ran as a standalone application
### #11
[FEAT] - [downloader](/src/downloader.rs)
Refractor for better readability and usage
Refractor downloader for better readability and usage
### #12
[GIT]
Add ci that runs clippy and builds in release mode
Constant todos:
TODO: Run code through clippy and fix any errors
### #13
[FEAT] - [assets](/assets/)
Make new icons for the app, preferably svg, except the app icon must be both svg and png
### #14
[FEAT] - [manifest](/src/manifest/) [downloader](/src/downloader.rs)
Add custom type for downloading, one for simple http downloads, and archived ones (zip, 7z, etc)
### #15
[FEAT] - [dependencies](/Cargo.toml)
Clean up dependencies, remove unneeded features for executable size
### #16
[FEAT] - [song_list](/src/ui/gui/components/song_list/mod.rs)
Add a checkmark or an X depending on if the song is downloaded to disk

View File

@ -10,7 +10,7 @@ The music is downloaded via [ytdlp](#Dependencies) and [spotdl](#Dependencies),
## Offline usage
Your whole music library is downloaded to your music folder (unless its being ran in standalone mode). Saved in your selected format.
All of the info required to download your songs is stored in 1 file (!). So all you need to backup all of your music is just 1 relatively small file AND you get the added benefit of easily moving your music between devices with just 1 manifest file, 1 executable (and 3 [dependencies](#dependencies)). Just press `download all` and see as all of your playlists appear in your hard drive, powereded by Open Source software.
All of the info required to download your songs is stored in 1 file (!). So all you need to backup all of your music is just 1 relatively small file AND you get the added benefit of easily moving your music between devices with just 1 manifest file, 1 executable (and 3 [dependencies](#dependencies)). Just press `download all` and see as all of your playlists appear in your hard drive, powered by Open Source software.
## Dependencies
[ffmpeg](https://ffmpeg.org/): To convert your music files to your desired format.

2
rust-toolchain.toml Normal file
View File

@ -0,0 +1,2 @@
[toolchain]
channel="nightly"

View File

@ -82,10 +82,10 @@ impl Downloader {
for (name, playlist) in manifest.get_playlists() {
for (song_name, song) in playlist.get_songs() {
self.download_song(cfg, song_name, song, name, format)?;
self.count += crate::process_manager::wait_for_procs_untill(10)?;
self.count += crate::process_manager::wait_for_procs_until(10)?;
}
}
self.count += crate::process_manager::wait_for_procs_untill(0)?;
self.count += crate::process_manager::wait_for_procs_until(0)?;
Ok(self.count)
}
@ -168,10 +168,6 @@ impl Downloader {
]);
cmd
}
url => {
log::error!("Unknown or unsupported hostname '{:?}'", url);
return Ok(());
}
};
if log::max_level() < Level::Debug {

View File

@ -66,6 +66,12 @@ impl Manifest {
pub fn get_playlists_mut(&mut self) -> &mut HashMap<String, playlist::Playlist> {
&mut self.playlists
}
pub fn remove_playlist(&mut self, playlist_name: &String) -> Option<playlist::Playlist> {
self.playlists.remove(playlist_name)
}
pub fn remove_song(&mut self, playlist_name: &String, song_name: &String) -> Option<Song> {
self.get_playlist_mut(playlist_name)?.remove_song(song_name)
}
pub fn get_song_count(&self) -> usize {
let mut count = 0;
for v in self.playlists.values() {

View File

@ -65,7 +65,7 @@ pub fn purge_done_procs() -> usize {
}
/// Waits for processes to finish until the proc count is lower or equal to `max`
pub fn wait_for_procs_untill(max: usize) -> anyhow::Result<usize> {
pub fn wait_for_procs_until(max: usize) -> anyhow::Result<usize> {
// NOTE: This looks really fucked because i dont want to deadlock the processes so i lock PROCESSES for as little as possible
// NOTE: So its also kinda really slow
let mut finish_count = 0;

View File

@ -27,7 +27,7 @@ pub fn song(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downl
if should_download {
downloader.download_song(cfg, name, &song, playlist, manifest.get_format())?;
crate::process_manager::wait_for_procs_untill(0)?;
crate::process_manager::wait_for_procs_until(0)?;
}
Ok(())

View File

@ -1,57 +0,0 @@
use egui::{Color32, RichText};
use crate::{manifest::song::Song, ui::gui::windows::{song_edit::GuiSongEditor, WindowIndex}};
pub struct ContextMenu;
// NOTE: This should be a component but theres no easy way to do that, so we just make it folow the
// trait manually, ish, more like a convention
impl /* ComponentUi for */ ContextMenu {
pub fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui, pname: &String, sname: &String, song: &Song) {
if ui.button("Edit").clicked() {
let w = gui.windows.get_window::<GuiSongEditor>(WindowIndex::SongEdit);
w.set_active_song(pname, sname, song.get_url_str(), song.get_type());
gui.windows.open(WindowIndex::SongEdit, true);
ui.close_menu();
}
if ui.button("Download").clicked() {
if let Err(e) = gui.downloader.download_song_nb(&gui.cfg, pname, sname, song, gui.manifest.get_format()) {
log::error!("{e}");
gui.throw_error(format!("Failed to download song {sname}: {e}"));
}
ui.close_menu();
}
if ui.button("Open Source").clicked() {
if let Err(e) = open::that(song.get_url_str()) {
log::error!("{e}");
gui.throw_error(format!("Failed to open song source: {e}"));
}
ui.close_menu();
}
if ui.button("Play").clicked() {
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format());
if !p.exists() {
gui.throw_error("Song does not exist on disk".to_string());
} else if let Err(e) = open::that(p) {
log::error!("{e}");
gui.throw_error(format!("Failed to play song: {e}"));
}
ui.close_menu();
}
if ui.button("Delete from disk").clicked() {
let p = crate::util::get_song_path(pname, sname, gui.manifest.get_format());
if p.exists() {
if let Err(e) = std::fs::remove_file(p) {
gui.throw_error(format!("Failed to delete file: {e}"));
}
}
ui.close_menu();
}
if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() {
gui.throw_error("TODO");
ui.close_menu();
}
}
}

View File

@ -3,7 +3,6 @@ use super::Gui;
pub mod nav;
pub mod song_list;
pub mod context_menu;
pub mod side_nav;
pub mod search_bar;
@ -18,3 +17,8 @@ pub trait ComponentUi {
pub trait ComponentUiMut {
fn ui(&mut self, gui: &mut Gui, ui: &mut egui::Ui);
}
pub trait ComponentContextMenu {
type Data;
fn ui(gui: &mut Gui, ui: &mut egui::Ui, data: &Self::Data);
}

View File

@ -2,10 +2,7 @@ use crate::ui::gui::{windows::WindowIndex, Gui};
use super::Component;
#[allow(clippy::pedantic)]
pub struct NavBar;
#[warn(clippy::pedantic)]
impl Component for NavBar {
fn ui(gui: &mut Gui, ctx: &egui::Context) {

View File

@ -1,46 +0,0 @@
use std::borrow::BorrowMut;
use egui::{Button, Color32, Label, RichText, Sense};
use super::ComponentUi;
pub struct SideNav;
impl ComponentUi for SideNav {
fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui) {
let mut playlist_names = gui.manifest
.get_playlists()
.keys().cloned().collect::<Vec<String>>();
playlist_names.sort_by_key(|name| name.to_lowercase());
ui.with_layout(egui::Layout::top_down(egui::Align::TOP), |ui| {
for pname in playlist_names {
if gui.current_playlist.is_empty() {
gui.current_playlist = pname.to_string();
}
ui.horizontal(|ui| {
let tint = Color32::from_hex("#333377").unwrap();
ui.add(egui::Image::new(crate::data::NOTE_ICON).tint(tint));
ui.horizontal(|ui| {
let text;
if gui.current_playlist == *pname {
text = RichText::new(&pname).color(tint);
} else {
text = RichText::new(&pname);
}
let button = Label::new(text).sense(Sense::click()).selectable(false);
if ui.add(button).clicked() {
gui.current_playlist = pname.to_string();
}
});
});
}
});
}
// #333377
}

View File

@ -0,0 +1,55 @@
use egui::{Color32, RichText};
use crate::ui::gui::{components::ComponentContextMenu, windows::{self, WindowIndex}};
#[derive(Debug)]
pub struct ContextMenu;
impl ComponentContextMenu for ContextMenu {
type Data = String; // Playlist name
fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui, playlist_name: &Self::Data) {
if ui.button("Edit").clicked() {
ui.close_menu();
}
if ui.button("Download all").clicked() {
let Some(playlist) = gui.manifest.get_playlist(playlist_name) else {
gui.throw_error(format!("Playlist not found: {}", playlist_name));
ui.close_menu();
return;
};
for (song_name, song) in playlist.get_songs() {
if let Err(e) = gui.downloader.download_song_nb(&gui.cfg, playlist_name, song_name, song, gui.manifest.get_format()) {
gui.throw_error(format!("Could not download song: {e}"));
ui.close_menu();
return;
}
}
ui.close_menu();
}
if ui.button("Delete from disk").clicked() {
let p = crate::util::get_playlist_path(playlist_name);
if p.exists() {
if let Err(e) = std::fs::remove_dir_all(p) {
gui.throw_error(format!("Failed to delete directory: {e}"));
}
}
ui.close_menu();
}
if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() {
let w = gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm);
w.set_message(
"side_nav_playlist_manifest_delete",
"This will delete the playlist from the manifest file. This is NOT reversible",
&[playlist_name.clone()]
);
gui.windows.open(WindowIndex::Confirm, true);
ui.close_menu();
}
}
}

View File

@ -0,0 +1,72 @@
use egui::{Color32, Label, RichText, Sense};
use crate::ui::gui::windows::{self, WindowIndex};
use super::{ComponentContextMenu, ComponentUi};
mod context_menu;
pub struct SideNav;
impl ComponentUi for SideNav {
fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui) {
let mut playlist_names = gui.manifest
.get_playlists()
.keys().cloned().collect::<Vec<String>>();
playlist_names.sort_by_key(|name| name.to_lowercase());
ui.with_layout(egui::Layout::top_down(egui::Align::TOP), |ui| {
for pname in playlist_names {
if gui.current_playlist.is_empty() {
gui.current_playlist = pname.to_string();
}
ui.horizontal(|ui| {
let tint = Color32::from_hex("#333377").unwrap();
ui.add(egui::Image::new(crate::data::NOTE_ICON).tint(tint))
.context_menu(|ui| context_menu::ContextMenu::ui(gui, ui, &pname));
ui.horizontal(|ui| {
let text = if gui.current_playlist == *pname {
RichText::new(&pname).color(tint)
} else {
RichText::new(&pname)
};
let button = Label::new(text).sense(Sense::click()).selectable(false);
let button = ui.add(button);
if button.clicked() {
gui.current_playlist = pname.to_string();
}
button.context_menu(|ui| context_menu::ContextMenu::ui(gui, ui, &pname));
});
});
}
});
check_if_needs_delete(gui);
}
// #333377
}
fn check_if_needs_delete(gui: &mut crate::ui::gui::Gui) {
// Check for items that need to be deleted
let (id, resp, data) = gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).get_response();
match (id.as_str(), resp) {
("side_nav_playlist_manifest_delete", Some(true)) => {
gui.manifest.remove_playlist(&data[0]);
let _ = gui.manifest.save(None);
gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).reset();
}
("side_nav_playlist_manifest_delete", Some(false)) => {
log::debug!("FALSE");
gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).reset();
}
_ => ()
}
}

View File

@ -0,0 +1,96 @@
use egui::{Color32, RichText};
use crate::{manifest::song::{Song, SongType}, ui::gui::windows::{self, song_edit::GuiSongEditor, WindowIndex}};
use super::ComponentContextMenu;
pub struct ContextMenu;
pub struct SongInfo {
pname: String,
sname: String,
song: Song,
}
impl SongInfo {
pub fn new(pname: &str, sname: &str, song: &Song) -> Self {
Self {
pname: pname.to_string(),
sname: sname.to_string(),
song: song.clone()
}
}
pub fn playlist_name(&self) -> &String {
&self.pname
}
pub fn song_name(&self) -> &String {
&self.sname
}
pub fn song_url(&self) -> &String {
self.song.get_url_str()
}
pub fn song_type(&self) -> &SongType {
self.song.get_type()
}
pub fn song(&self) -> &Song {
&self.song
}
}
impl ComponentContextMenu for ContextMenu {
type Data = SongInfo;
fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui, data: &Self::Data) {
if ui.button("Edit").clicked() {
let w = gui.windows.get_window::<GuiSongEditor>(WindowIndex::SongEdit);
w.set_active_song(data.playlist_name(), data.song_name(), data.song_url(), data.song_type());
gui.windows.open(WindowIndex::SongEdit, true);
ui.close_menu();
}
if ui.button("Download").clicked() {
if let Err(e) = gui.downloader.download_song_nb(&gui.cfg, data.playlist_name(), data.song_name(), data.song(), gui.manifest.get_format()) {
log::error!("{e}");
gui.throw_error(format!("Failed to download song {}: {e}", data.song_name()));
}
ui.close_menu();
}
if ui.button("Open Source").clicked() {
if let Err(e) = open::that(data.song_url()) {
log::error!("{e}");
gui.throw_error(format!("Failed to open song source: {e}"));
}
ui.close_menu();
}
if ui.button("Play").clicked() {
let p = crate::util::get_song_path(data.playlist_name(), data.song_name(), gui.manifest.get_format());
if !p.exists() {
gui.throw_error("Song does not exist on disk".to_string());
} else if let Err(e) = open::that(p) {
log::error!("{e}");
gui.throw_error(format!("Failed to play song: {e}"));
}
ui.close_menu();
}
if ui.button("Delete from disk").clicked() {
let p = crate::util::get_song_path(data.playlist_name(), data.song_name(), gui.manifest.get_format());
if p.exists() {
if let Err(e) = std::fs::remove_file(p) {
gui.throw_error(format!("Failed to delete file: {e}"));
}
}
ui.close_menu();
}
if ui.button(RichText::new("Delete").color(Color32::RED)).clicked() {
let w = gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm);
w.set_message(
"song_list_song_manifest_delete",
"This will delete the song from the manifest file. This is NOT reversible",
&[data.playlist_name().clone(), data.song_name().clone()]
);
gui.windows.open(WindowIndex::Confirm, true);
ui.close_menu();
ui.close_menu();
}
}
}

View File

@ -1,13 +1,14 @@
use egui::Color32;
use egui_extras::{Column, TableBuilder};
use crate::manifest::song::SongType;
use crate::{manifest::song::SongType, ui::gui::windows::{self, WindowIndex}};
use super::{context_menu::ContextMenu, search_bar::SearchType, ComponentUi};
use super::{search_bar::SearchType, ComponentContextMenu, ComponentUi};
mod context_menu;
#[derive(Debug, Default)]
pub struct SongList {
}
pub struct SongList;
impl ComponentUi for SongList {
fn ui(gui: &mut crate::ui::gui::Gui, ui: &mut egui::Ui) {
@ -25,17 +26,8 @@ impl ComponentUi for SongList {
.striped(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.resizable(true)
//.column(Column::auto())
//.column(Column::auto())
//.column(
// Column::remainder()
// .at_least(40.0)
// .clip(true)
// .resizable(true),
//)
.column(Column::auto())
.column(Column::remainder())
//.column(Column::remainder())
.min_scrolled_height(0.0)
.max_scroll_height(available_height)
.sense(egui::Sense::click());
@ -55,10 +47,6 @@ impl ComponentUi for SongList {
};
table.header(20.0, |mut header| {
// header.col(|_|{});
//header.col(|ui| {
// ui.strong("Playlist");vec.sort_by_key(|name| name.to_lowercase());
//});
header.col(|ui| {
ui.strong("Source");
});
@ -97,11 +85,8 @@ impl ComponentUi for SongList {
(SearchType::Url, _) => (),
}
body.row(18.0, |mut row| {
//row.col(|ui| {
// ui.label(pname.clone())
// .context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
//});
let song_info = context_menu::SongInfo::new(&pname, &sname, &s);
row.col(|ui| {
let color =
match s.get_type() {
@ -111,20 +96,41 @@ impl ComponentUi for SongList {
};
ui.colored_label(color, s.get_type().to_string())
.context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
.context_menu(|ui| context_menu::ContextMenu::ui(gui, ui, &song_info));
});
row.col(|ui| {
ui.hyperlink_to(sname.clone(), s.get_url_str())
.context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
.context_menu(|ui| context_menu::ContextMenu::ui(gui, ui, &song_info));
});
row.response()
.context_menu(|ui| ContextMenu::ui(gui, ui, &pname, &sname, &s));
.context_menu(|ui| context_menu::ContextMenu::ui(gui, ui, &song_info));
});
}
});
});
});
check_if_needs_delete(gui);
}
}
fn check_if_needs_delete(gui: &mut crate::ui::gui::Gui) {
// Check for items that need to be deleted
let (id, resp, data) = gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).get_response();
match (id.as_str(), resp) {
("song_list_song_manifest_delete", Some(true)) => {
gui.manifest.remove_song(&data[0], &data[1]);
let _ = gui.manifest.save(None);
gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).reset();
}
("song_list_song_manifest_delete", Some(false)) => {
log::debug!("FALSE");
gui.windows.get_window::<windows::confirm::ConfirmW>(WindowIndex::Confirm).reset();
}
_ => ()
}
}

View File

@ -51,10 +51,9 @@ impl Gui {
Ok(())
}
#[allow(clippy::pedantic)]
pub fn throw_error<S: ToString>(&mut self, text: S) {
let w = self.windows.get_window::<windows::error::GuiError>(WindowIndex::Error);
w.set_error_message(&text);
w.set_error_message(text);
self.windows.open(WindowIndex::Error, true);
}
}
@ -94,5 +93,7 @@ impl eframe::App for Gui {
});
});
});
// Make sure we dont wait for any updates cause we depend on the gui code for downloads
ctx.request_repaint();
}
}

View File

@ -0,0 +1,57 @@
use egui::{Color32, Label, RichText};
use super::{State, Window};
#[allow(clippy::pedantic)]
#[derive(Debug, Default)]
pub struct ConfirmW {
id: String,
text: String,
response: Option<bool>,
data: Vec<String>
}
impl Window for ConfirmW {
fn ui(&mut self, _: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()> {
let mut should_close = false;
egui::Window::new("Are you sure?").open(open).show(ctx, |ui| {
ui.vertical(|ui| {
ui.label(RichText::new("Are you sure you want to do this?").size(15.0).color(Color32::BLUE));
ui.horizontal(|ui| {
ui.add(Label::new(self.text.clone()).wrap(true));
});
ui.horizontal(|ui| {
if ui.button("Cancel").clicked() {
self.response = Some(false);
should_close = true;
} else if ui.button("Continue").clicked() {
self.response = Some(true);
should_close = true;
}
})
})
});
if should_close {
*open = false;
}
Ok(())
}
}
impl ConfirmW {
pub fn set_message<S: ToString>(&mut self, new_id: S, text: S, data: &[String]) {
self.text = text.to_string();
self.id = new_id.to_string();
self.data = data.to_vec();
}
pub fn get_response(&self) -> (&String, &Option<bool>, &Vec<String>) {
(&self.id, &self.response, &self.data)
}
pub fn reset(&mut self) {
self.id.clear();
self.text.clear();
self.response = None;
}
}

View File

@ -16,7 +16,7 @@ impl Window for GuiError {
.open(open)
.show(ctx, |ui| {
ui.vertical(|ui| {
ui.label(RichText::new("Error:").size(30.0).color(Color32::RED));
ui.label(RichText::new("Error:").size(15.0).color(Color32::RED));
ui.horizontal(|ui| {
ui.add(Label::new(self.text.clone()).wrap(true));
})
@ -27,7 +27,7 @@ impl Window for GuiError {
}
impl GuiError {
pub fn set_error_message<S: ToString>(&mut self, text: &S) {
pub fn set_error_message<S: ToString>(&mut self, text: S) {
self.text = text.to_string();
}
}

View File

@ -1,4 +1,4 @@
use crate::manifest::song::{Song, SongType};
use crate::manifest::song::SongType;
use super::{State, Window};
@ -9,8 +9,8 @@ pub struct GuiImportPlaylist {
ed_type: SongType,
ed_name: String,
ed_url: String,
urls_to_add: Vec<String>,
playlist_name: String,
//urls_to_add: Vec<String>,
// playlist_name: String,
}
@ -46,8 +46,8 @@ impl Window for GuiImportPlaylist {
}
});
if let Some(url) = self.urls_to_add.pop() {
todo!();
//if let Some(_) = self.urls_to_add.pop() {
// todo!();
//let client = reqwest::blocking::Client::new();
// let song_name = crate::crawler::spotify::get_song_name(&client, url.clone())?;
@ -57,7 +57,7 @@ impl Window for GuiImportPlaylist {
// playlist.add_song(song_name, song);
//}
//let _ = state.manifest.save(None);
}
//}
if save {

View File

@ -5,6 +5,7 @@ pub mod song_edit;
pub mod error;
pub mod import_playlist;
pub mod song_new;
pub mod confirm;
pub trait Window: std::fmt::Debug {
fn ui(&mut self, state: &mut State, ctx: &egui::Context, open: &mut bool) -> anyhow::Result<()>;
@ -16,6 +17,7 @@ pub enum WindowIndex {
ImportPlaylist,
SongEdit,
SongNew,
Confirm
}
@ -38,6 +40,7 @@ impl WindowManager {
windows.insert(WindowIndex::ImportPlaylist, Box::<import_playlist::GuiImportPlaylist>::default());
windows.insert(WindowIndex::SongEdit, Box::<song_edit::GuiSongEditor>::default());
windows.insert(WindowIndex::SongNew, Box::<song_new::GuiNewSong>::default());
windows.insert(WindowIndex::Confirm, Box::<confirm::ConfirmW>::default());
Self {
windows,
..Default::default()

View File

@ -70,4 +70,12 @@ pub fn get_song_path/*<P: TryInto<PathBuf>>*/(/*basepath: Option<P>,*/ pname: &S
path.push(sname);
path.set_extension(format.to_string());
path
}
pub fn get_playlist_path(pname: &String) -> PathBuf {
let mut path = std::env::current_dir().unwrap_or_default();
// TODO: Get this from cfg
path.push("out");
path.push(pname);
path
}