mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-06-19 04:03:33 +00:00
feat(gui): sorting options (#134)
* feat(gui): added an ability to copy ```pwsp-cli action play``` command for every sound * feat(gui): added files sorting options
This commit is contained in:
committed by
GitHub
parent
c173e602ad
commit
410a2c7959
@@ -70,6 +70,61 @@ kz = "Жою"
|
|||||||
he = "הסר"
|
he = "הסר"
|
||||||
pt-BR = "Remover"
|
pt-BR = "Remover"
|
||||||
|
|
||||||
|
[gui.context.dirs.sort_by]
|
||||||
|
en = "Sort by"
|
||||||
|
ru = "Сортировка"
|
||||||
|
es = "Ordenar por"
|
||||||
|
fr = "Trier par"
|
||||||
|
zh = "排序方式"
|
||||||
|
ar = "ترتيب حسب"
|
||||||
|
kz = "Сұрыптау"
|
||||||
|
he = "מיין לפי"
|
||||||
|
pt-BR = "Ordenar por"
|
||||||
|
|
||||||
|
[gui.sort.alpha_asc]
|
||||||
|
en = "Alphabetical (A-Z)"
|
||||||
|
ru = "По алфавиту (А-Я)"
|
||||||
|
es = "Alfabético (A-Z)"
|
||||||
|
fr = "Alphabétique (A-Z)"
|
||||||
|
zh = "字母顺序 (A-Z)"
|
||||||
|
ar = "أبجدي (A-Z)"
|
||||||
|
kz = "Әліпби бойынша (А-Я)"
|
||||||
|
he = "אלפביתי (A-Z)"
|
||||||
|
pt-BR = "Alfabético (A-Z)"
|
||||||
|
|
||||||
|
[gui.sort.alpha_desc]
|
||||||
|
en = "Alphabetical (Z-A)"
|
||||||
|
ru = "По алфавиту (Я-А)"
|
||||||
|
es = "Alfabético (Z-A)"
|
||||||
|
fr = "Alphabétique (Z-A)"
|
||||||
|
zh = "字母顺序 (Z-A)"
|
||||||
|
ar = "أبجدي (Z-A)"
|
||||||
|
kz = "Әліпби бойынша (Я-А)"
|
||||||
|
he = "אלפביתי (Z-A)"
|
||||||
|
pt-BR = "Alfabético (Z-A)"
|
||||||
|
|
||||||
|
[gui.sort.date_newest]
|
||||||
|
en = "Date modified (Newest first)"
|
||||||
|
ru = "Дата изменения (Сначала новые)"
|
||||||
|
es = "Fecha de modificación (Más nuevo primero)"
|
||||||
|
fr = "Date de modification (Plus récent en premier)"
|
||||||
|
zh = "修改日期 (最新优先)"
|
||||||
|
ar = "تاريخ التعديل (الأحدث أولاً)"
|
||||||
|
kz = "Өзгертілген күні (Жаңалары бірінші)"
|
||||||
|
he = "תאריך שינוי (החדש ביותר ראשון)"
|
||||||
|
pt-BR = "Data de modificação (Mais novo primeiro)"
|
||||||
|
|
||||||
|
[gui.sort.date_oldest]
|
||||||
|
en = "Date modified (Oldest first)"
|
||||||
|
ru = "Дата изменения (Сначала старые)"
|
||||||
|
es = "Fecha de modificación (Más antiguo primero)"
|
||||||
|
fr = "Date de modification (Plus ancien en premier)"
|
||||||
|
zh = "修改日期 (最旧优先)"
|
||||||
|
ar = "تاريخ التعديل (الأقدم أولاً)"
|
||||||
|
kz = "Өзгертілген күні (Ескілері бірінші)"
|
||||||
|
he = "תאריך שינוי (הישן ביותר ראשון)"
|
||||||
|
pt-BR = "Data de modificação (Mais antigo primeiro)"
|
||||||
|
|
||||||
[gui.context.files.play_solo]
|
[gui.context.files.play_solo]
|
||||||
en = "Play Solo"
|
en = "Play Solo"
|
||||||
ru = "Играть"
|
ru = "Играть"
|
||||||
|
|||||||
+22
-1
@@ -159,6 +159,13 @@ impl SoundpadGui {
|
|||||||
|
|
||||||
pub fn get_filtered_files(&self) -> Vec<PathBuf> {
|
pub fn get_filtered_files(&self) -> Vec<PathBuf> {
|
||||||
let mut files: Vec<PathBuf> = self.app_state.listed_files.iter().cloned().collect();
|
let mut files: Vec<PathBuf> = self.app_state.listed_files.iter().cloned().collect();
|
||||||
|
let sort_order = self
|
||||||
|
.app_state
|
||||||
|
.current_dir
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| self.config.get_sort_order(d))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
files.sort_by(|a, b| {
|
files.sort_by(|a, b| {
|
||||||
let a_is_dir = a.is_dir();
|
let a_is_dir = a.is_dir();
|
||||||
let b_is_dir = b.is_dir();
|
let b_is_dir = b.is_dir();
|
||||||
@@ -167,7 +174,7 @@ impl SoundpadGui {
|
|||||||
} else if !a_is_dir && b_is_dir {
|
} else if !a_is_dir && b_is_dir {
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
} else {
|
} else {
|
||||||
a.cmp(b)
|
sort_order.compare(a, b)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -334,5 +341,19 @@ mod tests {
|
|||||||
let filtered_search = gui.get_filtered_files();
|
let filtered_search = gui.get_filtered_files();
|
||||||
assert_eq!(filtered_search.len(), 1);
|
assert_eq!(filtered_search.len(), 1);
|
||||||
assert_eq!(filtered_search[0], file_c);
|
assert_eq!(filtered_search[0], file_c);
|
||||||
|
|
||||||
|
// Test sort order descending
|
||||||
|
gui.app_state.current_dir = Some(PathBuf::from("dummy_dir"));
|
||||||
|
gui.config.dirs_settings.insert(
|
||||||
|
PathBuf::from("dummy_dir"),
|
||||||
|
pwsp_lib::types::config::DirSettings {
|
||||||
|
sort_order: pwsp_lib::types::config::SortOrder::AlphabeticalDesc,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
gui.app_state.search_query = String::new();
|
||||||
|
let filtered_desc = gui.get_filtered_files();
|
||||||
|
assert_eq!(filtered_desc.len(), 2);
|
||||||
|
assert_eq!(filtered_desc[0], file_c);
|
||||||
|
assert_eq!(filtered_desc[1], file_b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ use egui::{
|
|||||||
};
|
};
|
||||||
use egui_dnd::dnd;
|
use egui_dnd::dnd;
|
||||||
use egui_material_icons::icons::*;
|
use egui_material_icons::icons::*;
|
||||||
use pwsp_lib::types::{gui::AppState, gui::AudioPlayerState};
|
use pwsp_lib::types::{
|
||||||
|
config::{GuiConfig, SortOrder},
|
||||||
|
gui::{AppState, AudioPlayerState},
|
||||||
|
};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::{cmp::Ordering, path::Path, path::PathBuf};
|
use std::{cmp::Ordering, path::Path, path::PathBuf};
|
||||||
|
|
||||||
@@ -135,6 +138,65 @@ impl SoundpadGui {
|
|||||||
{
|
{
|
||||||
self.app_state.dirs_to_remove.insert(path.clone());
|
self.app_state.dirs_to_remove.insert(path.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.separator();
|
||||||
|
ui.label(t!("gui.context.dirs.sort_by"));
|
||||||
|
|
||||||
|
let current_order = self
|
||||||
|
.config
|
||||||
|
.dirs_settings
|
||||||
|
.get(path)
|
||||||
|
.map(|s| s.sort_order)
|
||||||
|
.unwrap_or_default();
|
||||||
|
let mut new_order = None;
|
||||||
|
|
||||||
|
if ui
|
||||||
|
.radio(
|
||||||
|
current_order == SortOrder::AlphabeticalAsc,
|
||||||
|
t!("gui.sort.alpha_asc"),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
new_order = Some(SortOrder::AlphabeticalAsc);
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.radio(
|
||||||
|
current_order == SortOrder::AlphabeticalDesc,
|
||||||
|
t!("gui.sort.alpha_desc"),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
new_order = Some(SortOrder::AlphabeticalDesc);
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.radio(
|
||||||
|
current_order == SortOrder::DateModifiedNewest,
|
||||||
|
t!("gui.sort.date_newest"),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
new_order = Some(SortOrder::DateModifiedNewest);
|
||||||
|
}
|
||||||
|
if ui
|
||||||
|
.radio(
|
||||||
|
current_order == SortOrder::DateModifiedOldest,
|
||||||
|
t!("gui.sort.date_oldest"),
|
||||||
|
)
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
new_order = Some(SortOrder::DateModifiedOldest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(order) = new_order {
|
||||||
|
self.config
|
||||||
|
.dirs_settings
|
||||||
|
.entry(path.clone())
|
||||||
|
.or_default()
|
||||||
|
.sort_order = order;
|
||||||
|
self.config.save_to_file().ok();
|
||||||
|
self.app_state.dir_cache.remove(path);
|
||||||
|
self.open_dir(path);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -192,6 +254,7 @@ impl SoundpadGui {
|
|||||||
Self::draw_tree_node(
|
Self::draw_tree_node(
|
||||||
ui,
|
ui,
|
||||||
entry_path,
|
entry_path,
|
||||||
|
&self.config,
|
||||||
&mut self.app_state,
|
&mut self.app_state,
|
||||||
&self.audio_player_state,
|
&self.audio_player_state,
|
||||||
&mut actions,
|
&mut actions,
|
||||||
@@ -226,6 +289,7 @@ impl SoundpadGui {
|
|||||||
fn draw_tree_node_dir(
|
fn draw_tree_node_dir(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
|
config: &GuiConfig,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
audio_player_state: &AudioPlayerState,
|
audio_player_state: &AudioPlayerState,
|
||||||
actions: &mut Vec<FileAction>,
|
actions: &mut Vec<FileAction>,
|
||||||
@@ -247,6 +311,7 @@ impl SoundpadGui {
|
|||||||
read.push(entry.path());
|
read.push(entry.path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let sort_order = config.get_sort_order(&path);
|
||||||
read.sort_by(|a, b| {
|
read.sort_by(|a, b| {
|
||||||
let a_is_dir = a.is_dir();
|
let a_is_dir = a.is_dir();
|
||||||
let b_is_dir = b.is_dir();
|
let b_is_dir = b.is_dir();
|
||||||
@@ -255,7 +320,7 @@ impl SoundpadGui {
|
|||||||
} else if !a_is_dir && b_is_dir {
|
} else if !a_is_dir && b_is_dir {
|
||||||
Ordering::Greater
|
Ordering::Greater
|
||||||
} else {
|
} else {
|
||||||
a.cmp(b)
|
sort_order.compare(a, b)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
app_state.dir_cache.insert(path.clone(), read.clone());
|
app_state.dir_cache.insert(path.clone(), read.clone());
|
||||||
@@ -287,7 +352,7 @@ impl SoundpadGui {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::draw_tree_node(ui, child, app_state, audio_player_state, actions);
|
Self::draw_tree_node(ui, child, config, app_state, audio_player_state, actions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -437,12 +502,13 @@ impl SoundpadGui {
|
|||||||
fn draw_tree_node(
|
fn draw_tree_node(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
|
config: &GuiConfig,
|
||||||
app_state: &mut AppState,
|
app_state: &mut AppState,
|
||||||
audio_player_state: &AudioPlayerState,
|
audio_player_state: &AudioPlayerState,
|
||||||
actions: &mut Vec<FileAction>,
|
actions: &mut Vec<FileAction>,
|
||||||
) {
|
) {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
Self::draw_tree_node_dir(ui, path, app_state, audio_player_state, actions);
|
Self::draw_tree_node_dir(ui, path, config, app_state, audio_player_state, actions);
|
||||||
} else {
|
} else {
|
||||||
Self::draw_tree_node_file(ui, path, app_state, audio_player_state, actions);
|
Self::draw_tree_node_file(ui, path, app_state, audio_player_state, actions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,13 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, fs, path::PathBuf};
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
collections::HashMap,
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::SystemTime,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default, Clone, Serialize, Deserialize)]
|
#[derive(Default, Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -45,6 +51,21 @@ pub enum PreferredTheme {
|
|||||||
Dark,
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum SortOrder {
|
||||||
|
#[default]
|
||||||
|
AlphabeticalAsc,
|
||||||
|
AlphabeticalDesc,
|
||||||
|
DateModifiedNewest,
|
||||||
|
DateModifiedOldest,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct DirSettings {
|
||||||
|
pub sort_order: SortOrder,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct GuiConfig {
|
pub struct GuiConfig {
|
||||||
@@ -57,10 +78,38 @@ pub struct GuiConfig {
|
|||||||
pub pause_on_exit: bool,
|
pub pause_on_exit: bool,
|
||||||
|
|
||||||
pub dirs: Vec<PathBuf>,
|
pub dirs: Vec<PathBuf>,
|
||||||
|
pub dirs_settings: HashMap<PathBuf, DirSettings>,
|
||||||
|
|
||||||
pub preferred_theme: PreferredTheme,
|
pub preferred_theme: PreferredTheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SortOrder {
|
||||||
|
pub fn compare(&self, a: &Path, b: &Path) -> Ordering {
|
||||||
|
match self {
|
||||||
|
SortOrder::AlphabeticalAsc => a.cmp(b),
|
||||||
|
SortOrder::AlphabeticalDesc => b.cmp(a),
|
||||||
|
SortOrder::DateModifiedNewest => {
|
||||||
|
let a_time = fs::metadata(a)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
|
let b_time = fs::metadata(b)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
|
b_time.cmp(&a_time)
|
||||||
|
}
|
||||||
|
SortOrder::DateModifiedOldest => {
|
||||||
|
let a_time = fs::metadata(a)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
|
let b_time = fs::metadata(b)
|
||||||
|
.and_then(|m| m.modified())
|
||||||
|
.unwrap_or(SystemTime::UNIX_EPOCH);
|
||||||
|
a_time.cmp(&b_time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for GuiConfig {
|
impl Default for GuiConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
GuiConfig {
|
GuiConfig {
|
||||||
@@ -75,11 +124,23 @@ impl Default for GuiConfig {
|
|||||||
dirs: vec![ensure_pwsp_audio_dir()],
|
dirs: vec![ensure_pwsp_audio_dir()],
|
||||||
|
|
||||||
preferred_theme: PreferredTheme::System,
|
preferred_theme: PreferredTheme::System,
|
||||||
|
dirs_settings: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GuiConfig {
|
impl GuiConfig {
|
||||||
|
pub fn get_sort_order(&self, path: &Path) -> SortOrder {
|
||||||
|
let mut current = Some(path);
|
||||||
|
while let Some(p) = current {
|
||||||
|
if let Some(settings) = self.dirs_settings.get(p) {
|
||||||
|
return settings.sort_order;
|
||||||
|
}
|
||||||
|
current = p.parent();
|
||||||
|
}
|
||||||
|
SortOrder::default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_to_file(&mut self) -> Result<()> {
|
pub fn save_to_file(&mut self) -> Result<()> {
|
||||||
let config_path = get_config_path()?.join("gui.json");
|
let config_path = get_config_path()?.join("gui.json");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user