Implement history, show operations, implement trash
This commit is contained in:
parent
004fd617ea
commit
12a2a39a9f
6 changed files with 344 additions and 119 deletions
185
src/main.rs
185
src/main.rs
|
|
@ -7,15 +7,22 @@ use cosmic::{
|
|||
cosmic_theme, executor,
|
||||
iced::{
|
||||
event,
|
||||
futures::SinkExt,
|
||||
keyboard::{Event as KeyEvent, KeyCode, Modifiers},
|
||||
subscription::Subscription,
|
||||
subscription::{self, Subscription},
|
||||
window, Event, Length, Point,
|
||||
},
|
||||
style,
|
||||
widget::{self, segmented_button},
|
||||
Application, ApplicationExt, Element,
|
||||
};
|
||||
use std::{any::TypeId, env, fs, path::PathBuf, process, collections::HashMap};
|
||||
use std::{
|
||||
any::TypeId,
|
||||
collections::{BTreeMap, HashMap},
|
||||
env, fs, io,
|
||||
path::PathBuf,
|
||||
process, time,
|
||||
};
|
||||
|
||||
use config::{AppTheme, Config, CONFIG_VERSION};
|
||||
mod config;
|
||||
|
|
@ -31,9 +38,10 @@ mod mouse_area;
|
|||
|
||||
mod mime_icon;
|
||||
|
||||
use operation::Operation;
|
||||
mod operation;
|
||||
|
||||
use tab::{Location, Tab};
|
||||
use tab::{ItemMetadata, Location, Tab};
|
||||
mod tab;
|
||||
|
||||
/// Runs application with these settings
|
||||
|
|
@ -146,8 +154,12 @@ impl Action {
|
|||
Action::TabNew => Message::TabNew,
|
||||
Action::TabNext => Message::TabNext,
|
||||
Action::TabPrev => Message::TabPrev,
|
||||
Action::TabViewGrid => Message::TabMessage(entity_opt, tab::Message::View(tab::View::Grid)),
|
||||
Action::TabViewList => Message::TabMessage(entity_opt, tab::Message::View(tab::View::List)),
|
||||
Action::TabViewGrid => {
|
||||
Message::TabMessage(entity_opt, tab::Message::View(tab::View::Grid))
|
||||
}
|
||||
Action::TabViewList => {
|
||||
Message::TabMessage(entity_opt, tab::Message::View(tab::View::List))
|
||||
}
|
||||
Action::WindowClose => Message::WindowClose,
|
||||
Action::WindowNew => Message::WindowNew,
|
||||
}
|
||||
|
|
@ -168,6 +180,9 @@ pub enum Message {
|
|||
NewFile(Option<segmented_button::Entity>),
|
||||
NewFolder(Option<segmented_button::Entity>),
|
||||
Paste(Option<segmented_button::Entity>),
|
||||
PendingComplete(u64),
|
||||
PendingError(u64, String),
|
||||
PendingProgress(u64, f32),
|
||||
RestoreFromTrash(Option<segmented_button::Entity>),
|
||||
SelectAll(Option<segmented_button::Entity>),
|
||||
SystemThemeModeChange(cosmic_theme::ThemeMode),
|
||||
|
|
@ -187,6 +202,7 @@ pub enum Message {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ContextPage {
|
||||
Operations,
|
||||
Properties,
|
||||
Settings,
|
||||
}
|
||||
|
|
@ -194,6 +210,7 @@ pub enum ContextPage {
|
|||
impl ContextPage {
|
||||
fn title(&self) -> String {
|
||||
match self {
|
||||
Self::Operations => fl!("operations"),
|
||||
Self::Properties => fl!("properties"),
|
||||
Self::Settings => fl!("settings"),
|
||||
}
|
||||
|
|
@ -211,6 +228,10 @@ pub struct App {
|
|||
context_page: ContextPage,
|
||||
key_binds: HashMap<KeyBind, Action>,
|
||||
modifiers: Modifiers,
|
||||
pending_operation_id: u64,
|
||||
pending_operations: BTreeMap<u64, (Operation, f32)>,
|
||||
complete_operations: BTreeMap<u64, Operation>,
|
||||
failed_operations: BTreeMap<u64, (Operation, String)>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -227,6 +248,15 @@ impl App {
|
|||
Command::batch([self.update_title(), self.rescan_tab(entity, location)])
|
||||
}
|
||||
|
||||
fn operation(&mut self, operation: Operation) {
|
||||
let id = self.pending_operation_id;
|
||||
self.pending_operation_id += 1;
|
||||
self.pending_operations.insert(id, (operation, 0.0));
|
||||
//TODO: have some button to show current status
|
||||
self.core.window.show_context = true;
|
||||
self.context_page = ContextPage::Operations;
|
||||
}
|
||||
|
||||
fn rescan_tab(
|
||||
&mut self,
|
||||
entity: segmented_button::Entity,
|
||||
|
|
@ -275,6 +305,47 @@ impl App {
|
|||
self.set_window_title(window_title)
|
||||
}
|
||||
|
||||
fn operations(&self) -> Element<Message> {
|
||||
let mut children = Vec::new();
|
||||
|
||||
//TODO: get height from theme?
|
||||
let progress_bar_height = Length::Fixed(4.0);
|
||||
|
||||
if !self.pending_operations.is_empty() {
|
||||
let mut section = widget::settings::view_section(fl!("pending"));
|
||||
for (id, (op, progress)) in self.pending_operations.iter() {
|
||||
section = section.add(widget::column::with_children(vec![
|
||||
widget::text(format!("{:?}", op)).into(),
|
||||
widget::progress_bar(0.0..=100.0, *progress)
|
||||
.height(progress_bar_height)
|
||||
.into(),
|
||||
]));
|
||||
}
|
||||
children.push(section.into());
|
||||
}
|
||||
|
||||
if !self.failed_operations.is_empty() {
|
||||
let mut section = widget::settings::view_section(fl!("failed"));
|
||||
for (id, (op, error)) in self.failed_operations.iter() {
|
||||
section = section.add(widget::column::with_children(vec![
|
||||
widget::text(format!("{:?}", op)).into(),
|
||||
widget::text(error).into(),
|
||||
]));
|
||||
}
|
||||
children.push(section.into());
|
||||
}
|
||||
|
||||
if !self.complete_operations.is_empty() {
|
||||
let mut section = widget::settings::view_section(fl!("complete"));
|
||||
for (id, op) in self.complete_operations.iter() {
|
||||
section = section.add(widget::text(format!("{:?}", op)));
|
||||
}
|
||||
children.push(section.into());
|
||||
}
|
||||
|
||||
widget::settings::view_column(children).into()
|
||||
}
|
||||
|
||||
fn properties(&self) -> Element<Message> {
|
||||
let mut children = Vec::new();
|
||||
let entity = self.tab_model.active();
|
||||
|
|
@ -388,6 +459,10 @@ impl Application for App {
|
|||
context_page: ContextPage::Settings,
|
||||
key_binds: key_binds(),
|
||||
modifiers: Modifiers::empty(),
|
||||
pending_operation_id: 0,
|
||||
pending_operations: BTreeMap::new(),
|
||||
complete_operations: BTreeMap::new(),
|
||||
failed_operations: BTreeMap::new(),
|
||||
};
|
||||
|
||||
let mut commands = Vec::new();
|
||||
|
|
@ -507,7 +582,20 @@ impl Application for App {
|
|||
self.modifiers = modifiers;
|
||||
}
|
||||
Message::MoveToTrash(entity_opt) => {
|
||||
log::warn!("TODO: MOVE TO TRASH");
|
||||
let mut paths = Vec::new();
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(ref mut items) = tab.items_opt {
|
||||
for item in items.iter_mut() {
|
||||
if item.selected {
|
||||
paths.push(item.path.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !paths.is_empty() {
|
||||
self.operation(Operation::Delete { paths });
|
||||
}
|
||||
}
|
||||
Message::NewFile(entity_opt) => {
|
||||
log::warn!("TODO: NEW FILE");
|
||||
|
|
@ -518,8 +606,43 @@ impl Application for App {
|
|||
Message::Paste(entity_opt) => {
|
||||
log::warn!("TODO: PASTE");
|
||||
}
|
||||
Message::PendingComplete(id) => {
|
||||
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
||||
self.complete_operations.insert(id, op);
|
||||
}
|
||||
}
|
||||
Message::PendingError(id, err) => {
|
||||
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
||||
self.failed_operations.insert(id, (op, err));
|
||||
}
|
||||
}
|
||||
Message::PendingProgress(id, new_progress) => {
|
||||
if let Some((_, progress)) = self.pending_operations.get_mut(&id) {
|
||||
*progress = new_progress;
|
||||
}
|
||||
}
|
||||
Message::RestoreFromTrash(entity_opt) => {
|
||||
log::warn!("TODO: RESTORE FROM TRASH");
|
||||
let mut paths = Vec::new();
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
if let Some(ref mut items) = tab.items_opt {
|
||||
for item in items.iter_mut() {
|
||||
if item.selected {
|
||||
match &item.metadata {
|
||||
ItemMetadata::Trash { entry, .. } => {
|
||||
paths.push(entry.clone());
|
||||
}
|
||||
_ => {
|
||||
//TODO: error on trying to restore non-trash file?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !paths.is_empty() {
|
||||
self.operation(Operation::Restore { paths });
|
||||
}
|
||||
}
|
||||
Message::SelectAll(entity_opt) => {
|
||||
let entity = entity_opt.unwrap_or_else(|| self.tab_model.active());
|
||||
|
|
@ -692,24 +815,18 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Some(match self.context_page {
|
||||
ContextPage::Operations => self.operations(),
|
||||
ContextPage::Properties => self.properties(),
|
||||
ContextPage::Settings => self.settings(),
|
||||
})
|
||||
}
|
||||
|
||||
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![
|
||||
menu::menu_bar(&self.key_binds).into(),
|
||||
//TODO: use theme defined space?
|
||||
widget::horizontal_space(Length::Fixed(32.0)).into(),
|
||||
]
|
||||
vec![menu::menu_bar(&self.key_binds).into()]
|
||||
}
|
||||
|
||||
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![
|
||||
//TODO: use defined space
|
||||
widget::horizontal_space(Length::Fixed(32.0)).into(),
|
||||
]
|
||||
vec![]
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -778,14 +895,12 @@ impl Application for App {
|
|||
struct ConfigSubscription;
|
||||
struct ThemeSubscription;
|
||||
|
||||
Subscription::batch([
|
||||
let mut subscriptions = vec![
|
||||
event::listen_with(|event, _status| match event {
|
||||
Event::Keyboard(KeyEvent::KeyPressed {
|
||||
key_code,
|
||||
modifiers,
|
||||
}) => {
|
||||
Some(Message::Key(modifiers, key_code))
|
||||
}
|
||||
}) => Some(Message::Key(modifiers, key_code)),
|
||||
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||
Some(Message::Modifiers(modifiers))
|
||||
}
|
||||
|
|
@ -821,6 +936,34 @@ impl Application for App {
|
|||
}
|
||||
Message::SystemThemeModeChange(update.config)
|
||||
}),
|
||||
])
|
||||
];
|
||||
|
||||
for (id, (pending_operation, _)) in self.pending_operations.iter() {
|
||||
//TODO: use recipe?
|
||||
let id = *id;
|
||||
let pending_operation = pending_operation.clone();
|
||||
subscriptions.push(subscription::channel(
|
||||
id,
|
||||
16,
|
||||
move |mut msg_tx| async move {
|
||||
match pending_operation.perform(id, &mut msg_tx).await {
|
||||
Ok(()) => {
|
||||
msg_tx.send(Message::PendingComplete(id)).await;
|
||||
}
|
||||
Err(err) => {
|
||||
msg_tx
|
||||
.send(Message::PendingError(id, err.to_string()))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(time::Duration::new(1, 0)).await;
|
||||
}
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
Subscription::batch(subscriptions)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
17
src/menu.rs
17
src/menu.rs
|
|
@ -13,7 +13,7 @@ use cosmic::{
|
|||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{fl, KeyBind, tab, Action, ContextPage, Location, Message, Tab};
|
||||
use crate::{fl, tab, Action, ContextPage, KeyBind, Location, Message, Tab};
|
||||
|
||||
macro_rules! menu_button {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
|
|
@ -144,19 +144,10 @@ pub fn menu_bar<'a>(key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message
|
|||
MenuTree::with_children(
|
||||
menu_root(fl!("view")),
|
||||
vec![
|
||||
menu_item(
|
||||
fl!("grid-view"),
|
||||
Action::TabViewGrid
|
||||
),
|
||||
menu_item(
|
||||
fl!("list-view"),
|
||||
Action::TabViewList
|
||||
),
|
||||
menu_item(fl!("grid-view"), Action::TabViewGrid),
|
||||
menu_item(fl!("list-view"), Action::TabViewList),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item(
|
||||
fl!("menu-settings"),
|
||||
Action::Settings,
|
||||
),
|
||||
menu_item(fl!("menu-settings"), Action::Settings),
|
||||
],
|
||||
),
|
||||
])
|
||||
|
|
|
|||
108
src/operation.rs
108
src/operation.rs
|
|
@ -1,57 +1,73 @@
|
|||
use std::path::PathBuf;
|
||||
use cosmic::iced::futures::{channel::mpsc, SinkExt};
|
||||
use std::{error::Error, future::Future, io, path::PathBuf, time};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
use crate::Message;
|
||||
|
||||
fn err_str<T: ToString>(err: T) -> String {
|
||||
err.to_string()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum Operation {
|
||||
/// Move a path to the trash
|
||||
Delete { path: PathBuf },
|
||||
/// Rename a path
|
||||
Rename { old: PathBuf, new: PathBuf },
|
||||
/// Copy items
|
||||
Copy { paths: Vec<PathBuf>, to: PathBuf },
|
||||
/// Move items to the trash
|
||||
Delete { paths: Vec<PathBuf> },
|
||||
/// Move items
|
||||
Move { paths: Vec<PathBuf>, to: PathBuf },
|
||||
/// Restore a path from the trash
|
||||
Restore { path: PathBuf },
|
||||
Restore { paths: Vec<trash::TrashItem> },
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn delete(path: impl Into<PathBuf>) -> Self {
|
||||
Self::Delete { path: path.into() }
|
||||
}
|
||||
/// Perform the operation
|
||||
pub async fn perform(self, id: u64, msg_tx: &mut mpsc::Sender<Message>) -> Result<(), String> {
|
||||
msg_tx.send(Message::PendingProgress(id, 0.0)).await;
|
||||
|
||||
pub fn rename(old: impl Into<PathBuf>, new: impl Into<PathBuf>) -> Self {
|
||||
Self::Rename {
|
||||
old: old.into(),
|
||||
new: new.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore(path: impl Into<PathBuf>) -> Self {
|
||||
Self::Restore { path: path.into() }
|
||||
}
|
||||
|
||||
pub fn reverse(self) -> Self {
|
||||
//TODO: IF ERROR, RETURN AN Operation THAT CAN UNDO THE CURRENT STATE
|
||||
//TODO: SAFELY HANDLE CANCEL
|
||||
match self {
|
||||
Self::Delete { path } => Self::Restore { path },
|
||||
Self::Rename { old, new } => Self::Rename { old: new, new: old },
|
||||
Self::Restore { path } => Self::Delete { path },
|
||||
Self::Delete { paths } => {
|
||||
let mut total = paths.len();
|
||||
let mut count = 0;
|
||||
for path in paths {
|
||||
tokio::task::spawn_blocking(|| trash::delete(path))
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
count += 1;
|
||||
msg_tx
|
||||
.send(Message::PendingProgress(
|
||||
id,
|
||||
100.0 * (count as f32) / (total as f32),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
Self::Restore { paths } => {
|
||||
let mut total = paths.len();
|
||||
let mut count = 0;
|
||||
for path in paths {
|
||||
tokio::task::spawn_blocking(|| trash::os_limited::restore_all([path]))
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
count += 1;
|
||||
msg_tx
|
||||
.send(Message::PendingProgress(
|
||||
id,
|
||||
100.0 * (count as f32) / (total as f32),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err("not implemented".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Operation;
|
||||
|
||||
#[test]
|
||||
fn operation() {
|
||||
assert_eq!(
|
||||
Operation::delete("foo").reverse(),
|
||||
Operation::restore("foo")
|
||||
);
|
||||
assert_eq!(
|
||||
Operation::rename("foo", "bar").reverse(),
|
||||
Operation::rename("bar", "foo")
|
||||
);
|
||||
assert_eq!(
|
||||
Operation::restore("foo").reverse(),
|
||||
Operation::delete("foo")
|
||||
);
|
||||
|
||||
msg_tx.send(Message::PendingProgress(id, 100.0)).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
src/tab.rs
137
src/tab.rs
|
|
@ -255,7 +255,7 @@ pub fn scan_path(tab_path: &PathBuf) -> Vec<Item> {
|
|||
|
||||
items.push(Item {
|
||||
name,
|
||||
metadata: ItemMetadata::Path(metadata, children),
|
||||
metadata: ItemMetadata::Path { metadata, children },
|
||||
hidden,
|
||||
path,
|
||||
icon_handle_grid,
|
||||
|
|
@ -316,7 +316,7 @@ pub fn scan_trash() -> Vec<Item> {
|
|||
};
|
||||
|
||||
let path = entry.original_path();
|
||||
let name = entry.name;
|
||||
let name = entry.name.clone();
|
||||
|
||||
//TODO: configurable size
|
||||
let (icon_handle_grid, icon_handle_list) = match metadata.size {
|
||||
|
|
@ -332,7 +332,7 @@ pub fn scan_trash() -> Vec<Item> {
|
|||
|
||||
items.push(Item {
|
||||
name,
|
||||
metadata: ItemMetadata::Trash(metadata),
|
||||
metadata: ItemMetadata::Trash { metadata, entry },
|
||||
hidden: false,
|
||||
path,
|
||||
icon_handle_grid,
|
||||
|
|
@ -373,6 +373,8 @@ impl Location {
|
|||
pub enum Message {
|
||||
Click(Option<usize>),
|
||||
EditLocation(Option<Location>),
|
||||
GoNext,
|
||||
GoPrevious,
|
||||
Location(Location),
|
||||
RightClick(usize),
|
||||
View(View),
|
||||
|
|
@ -380,15 +382,21 @@ pub enum Message {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ItemMetadata {
|
||||
Path(Metadata, usize),
|
||||
Trash(trash::TrashItemMetadata),
|
||||
Path {
|
||||
metadata: Metadata,
|
||||
children: usize,
|
||||
},
|
||||
Trash {
|
||||
metadata: trash::TrashItemMetadata,
|
||||
entry: trash::TrashItem,
|
||||
},
|
||||
}
|
||||
|
||||
impl ItemMetadata {
|
||||
pub fn is_dir(&self) -> bool {
|
||||
match self {
|
||||
Self::Path(metadata, _) => metadata.is_dir(),
|
||||
Self::Trash(metadata) => match metadata.size {
|
||||
Self::Path { metadata, .. } => metadata.is_dir(),
|
||||
Self::Trash { metadata, .. } => match metadata.size {
|
||||
trash::TrashItemSize::Entries(_) => true,
|
||||
trash::TrashItemSize::Bytes(_) => false,
|
||||
},
|
||||
|
|
@ -421,7 +429,7 @@ impl Item {
|
|||
//TODO: translate!
|
||||
//TODO: correct display of folder size?
|
||||
match &self.metadata {
|
||||
ItemMetadata::Path(metadata, children) => {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
if metadata.is_dir() {
|
||||
section = section.add(widget::settings::item::item(
|
||||
"Items",
|
||||
|
|
@ -467,7 +475,7 @@ impl Item {
|
|||
));
|
||||
}
|
||||
}
|
||||
ItemMetadata::Trash(_metadata) => {
|
||||
ItemMetadata::Trash { .. } => {
|
||||
//TODO: trash metadata
|
||||
}
|
||||
}
|
||||
|
|
@ -504,16 +512,21 @@ pub struct Tab {
|
|||
pub items_opt: Option<Vec<Item>>,
|
||||
pub view: View,
|
||||
pub edit_location: Option<Location>,
|
||||
pub history_i: usize,
|
||||
pub history: Vec<Location>,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new(location: Location) -> Self {
|
||||
let history = vec![location.clone()];
|
||||
Self {
|
||||
location,
|
||||
context_menu: None,
|
||||
items_opt: None,
|
||||
view: View::List,
|
||||
edit_location: None,
|
||||
history_i: 0,
|
||||
history,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,6 +544,7 @@ impl Tab {
|
|||
|
||||
pub fn update(&mut self, message: Message, modifiers: Modifiers) -> bool {
|
||||
let mut cd = None;
|
||||
let mut history_i_opt = None;
|
||||
match message {
|
||||
Message::Click(click_i_opt) => {
|
||||
if let Some(ref mut items) = self.items_opt {
|
||||
|
|
@ -579,6 +593,22 @@ impl Tab {
|
|||
Message::EditLocation(edit_location) => {
|
||||
self.edit_location = edit_location;
|
||||
}
|
||||
Message::GoNext => {
|
||||
if let Some(history_i) = self.history_i.checked_add(1) {
|
||||
if let Some(location) = self.history.get(history_i) {
|
||||
cd = Some(location.clone());
|
||||
history_i_opt = Some(history_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::GoPrevious => {
|
||||
if let Some(history_i) = self.history_i.checked_sub(1) {
|
||||
if let Some(location) = self.history.get(history_i) {
|
||||
cd = Some(location.clone());
|
||||
history_i_opt = Some(history_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Location(location) => {
|
||||
cd = Some(location);
|
||||
}
|
||||
|
|
@ -603,11 +633,22 @@ impl Tab {
|
|||
self.view = view;
|
||||
}
|
||||
}
|
||||
if let Some(location) = cd {
|
||||
if let Some(mut location) = cd {
|
||||
if location != self.location {
|
||||
self.location = location;
|
||||
self.location = location.clone();
|
||||
self.items_opt = None;
|
||||
self.edit_location = None;
|
||||
if let Some(history_i) = history_i_opt {
|
||||
// Navigating in history
|
||||
self.history_i = history_i;
|
||||
} else {
|
||||
// Truncate history to remove next entries
|
||||
self.history.truncate(self.history_i + 1);
|
||||
|
||||
// Push to the front of history
|
||||
self.history_i = self.history.len();
|
||||
self.history.push(location);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
@ -617,36 +658,64 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn breadcrumbs_view(&self, core: &Core) -> Element<Message> {
|
||||
pub fn location_view(&self, core: &Core) -> Element<Message> {
|
||||
let cosmic_theme::Spacing {
|
||||
space_xxxs,
|
||||
space_xxs,
|
||||
space_s,
|
||||
..
|
||||
} = core.system_theme().cosmic().spacing;
|
||||
|
||||
let mut row = widget::row::with_capacity(5).align_items(Alignment::Center);
|
||||
|
||||
let mut prev_button =
|
||||
widget::button(widget::icon::from_name("go-previous-symbolic").size(16))
|
||||
.padding(space_xxs)
|
||||
.style(theme::Button::Icon);
|
||||
if self.history_i > 0 && !self.history.is_empty() {
|
||||
prev_button = prev_button.on_press(Message::GoPrevious);
|
||||
}
|
||||
row = row.push(prev_button);
|
||||
|
||||
let mut next_button = widget::button(widget::icon::from_name("go-next-symbolic").size(16))
|
||||
.padding(space_xxs)
|
||||
.style(theme::Button::Icon);
|
||||
if self.history_i + 1 < self.history.len() {
|
||||
next_button = next_button.on_press(Message::GoNext);
|
||||
}
|
||||
row = row.push(next_button);
|
||||
|
||||
row = row.push(widget::horizontal_space(Length::Fixed(space_s.into())));
|
||||
|
||||
if let Some(location) = &self.edit_location {
|
||||
match location {
|
||||
Location::Path(path) => {
|
||||
return widget::row::with_children(vec![
|
||||
row = row.push(
|
||||
widget::button(widget::icon::from_name("window-close-symbolic").size(16))
|
||||
.on_press(Message::EditLocation(None))
|
||||
.padding(space_xxs)
|
||||
.style(theme::Button::Icon)
|
||||
.into(),
|
||||
.style(theme::Button::Icon),
|
||||
);
|
||||
row = row.push(
|
||||
widget::text_input("", path.to_string_lossy())
|
||||
.on_input(|input| {
|
||||
Message::EditLocation(Some(Location::Path(PathBuf::from(input))))
|
||||
})
|
||||
.on_submit(Message::Location(location.clone()))
|
||||
.into(),
|
||||
])
|
||||
.align_items(Alignment::Center)
|
||||
.into();
|
||||
.on_submit(Message::Location(location.clone())),
|
||||
);
|
||||
return row.into();
|
||||
}
|
||||
_ => {
|
||||
//TODO: allow editing other locations
|
||||
}
|
||||
}
|
||||
} else {
|
||||
row = row.push(
|
||||
widget::button(widget::icon::from_name("edit-symbolic").size(16))
|
||||
.on_press(Message::EditLocation(Some(self.location.clone())))
|
||||
.padding(space_xxs)
|
||||
.style(theme::Button::Icon),
|
||||
);
|
||||
}
|
||||
|
||||
let mut children: Vec<Element<_>> = Vec::new();
|
||||
|
|
@ -726,18 +795,10 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
|
||||
children.insert(
|
||||
0,
|
||||
widget::button(widget::icon::from_name("edit-symbolic").size(16))
|
||||
.on_press(Message::EditLocation(Some(self.location.clone())))
|
||||
.padding(space_xxs)
|
||||
.style(theme::Button::Icon)
|
||||
.into(),
|
||||
);
|
||||
|
||||
widget::row::with_children(children)
|
||||
.align_items(Alignment::Center)
|
||||
.into()
|
||||
for child in children {
|
||||
row = row.push(child);
|
||||
}
|
||||
row.into()
|
||||
}
|
||||
|
||||
pub fn empty_view(&self, has_hidden: bool, core: &Core) -> Element<Message> {
|
||||
|
|
@ -815,7 +876,7 @@ impl Tab {
|
|||
}
|
||||
}
|
||||
widget::scrollable(widget::column::with_children(vec![
|
||||
self.breadcrumbs_view(core),
|
||||
self.location_view(core),
|
||||
widget::flex_row(children).into(),
|
||||
]))
|
||||
.width(Length::Fill)
|
||||
|
|
@ -830,7 +891,7 @@ impl Tab {
|
|||
|
||||
let mut children: Vec<Element<_>> = Vec::new();
|
||||
|
||||
children.push(self.breadcrumbs_view(core));
|
||||
children.push(self.location_view(core));
|
||||
|
||||
children.push(
|
||||
widget::row::with_children(vec![
|
||||
|
|
@ -868,24 +929,24 @@ impl Tab {
|
|||
}
|
||||
|
||||
let modified_text = match &item.metadata {
|
||||
ItemMetadata::Path(metadata, _children) => match metadata.modified() {
|
||||
ItemMetadata::Path { metadata, .. } => match metadata.modified() {
|
||||
Ok(time) => chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format("%c")
|
||||
.to_string(),
|
||||
Err(_) => String::new(),
|
||||
},
|
||||
ItemMetadata::Trash(metadata) => String::new(),
|
||||
ItemMetadata::Trash { .. } => String::new(),
|
||||
};
|
||||
|
||||
let size_text = match &item.metadata {
|
||||
ItemMetadata::Path(metadata, children) => {
|
||||
ItemMetadata::Path { metadata, children } => {
|
||||
if metadata.is_dir() {
|
||||
format!("{} items", children)
|
||||
} else {
|
||||
format_size(metadata.len())
|
||||
}
|
||||
}
|
||||
ItemMetadata::Trash(metadata) => match metadata.size {
|
||||
ItemMetadata::Trash { metadata, .. } => match metadata.size {
|
||||
trash::TrashItemSize::Entries(entries) => {
|
||||
//TODO: translate
|
||||
if entries == 1 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue