Merge branch 'master' into master
This commit is contained in:
commit
8efc6084ec
8 changed files with 310 additions and 393 deletions
100
src/app.rs
100
src/app.rs
|
|
@ -66,6 +66,7 @@ pub enum Action {
|
|||
AddToSidebar,
|
||||
Copy,
|
||||
Cut,
|
||||
EditHistory,
|
||||
EditLocation,
|
||||
HistoryNext,
|
||||
HistoryPrevious,
|
||||
|
|
@ -82,7 +83,6 @@ pub enum Action {
|
|||
OpenInNewWindow,
|
||||
OpenTerminal,
|
||||
OpenWith,
|
||||
Operations,
|
||||
Paste,
|
||||
Properties,
|
||||
Rename,
|
||||
|
|
@ -113,6 +113,7 @@ impl Action {
|
|||
Action::AddToSidebar => Message::AddToSidebar(entity_opt),
|
||||
Action::Copy => Message::Copy(entity_opt),
|
||||
Action::Cut => Message::Cut(entity_opt),
|
||||
Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory),
|
||||
Action::EditLocation => Message::EditLocation(entity_opt),
|
||||
Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext),
|
||||
Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious),
|
||||
|
|
@ -129,7 +130,6 @@ impl Action {
|
|||
Action::OpenInNewWindow => Message::OpenInNewWindow(entity_opt),
|
||||
Action::OpenTerminal => Message::OpenTerminal(entity_opt),
|
||||
Action::OpenWith => Message::ToggleContextPage(ContextPage::OpenWith),
|
||||
Action::Operations => Message::ToggleContextPage(ContextPage::Operations),
|
||||
Action::Paste => Message::Paste(entity_opt),
|
||||
Action::Properties => Message::ToggleContextPage(ContextPage::Properties(None)),
|
||||
Action::Rename => Message::Rename(entity_opt),
|
||||
|
|
@ -247,7 +247,9 @@ pub enum Message {
|
|||
TabMessage(Option<Entity>, tab::Message),
|
||||
TabNew,
|
||||
TabRescan(Entity, Location, Vec<tab::Item>),
|
||||
Toast(widget::toaster::ToastMessage),
|
||||
ToggleContextPage(ContextPage),
|
||||
Undo(u64),
|
||||
WindowClose,
|
||||
WindowNew,
|
||||
DndHoverLocTimeout(Location),
|
||||
|
|
@ -260,11 +262,17 @@ pub enum Message {
|
|||
DndDropNav(Entity, Option<ClipboardPaste>, DndAction),
|
||||
}
|
||||
|
||||
impl From<widget::toaster::ToastMessage> for Message {
|
||||
fn from(toast_message: widget::toaster::ToastMessage) -> Self {
|
||||
Self::Toast(toast_message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ContextPage {
|
||||
About,
|
||||
EditHistory,
|
||||
OpenWith,
|
||||
Operations,
|
||||
Properties(Option<ContextItem>),
|
||||
Settings,
|
||||
}
|
||||
|
|
@ -273,8 +281,8 @@ impl ContextPage {
|
|||
fn title(&self) -> String {
|
||||
match self {
|
||||
Self::About => String::new(),
|
||||
Self::EditHistory => fl!("edit-history"),
|
||||
Self::OpenWith => fl!("open-with"),
|
||||
Self::Operations => fl!("operations"),
|
||||
Self::Properties(..) => fl!("properties"),
|
||||
Self::Settings => fl!("settings"),
|
||||
}
|
||||
|
|
@ -357,6 +365,7 @@ pub struct App {
|
|||
search_active: bool,
|
||||
search_id: widget::Id,
|
||||
search_input: String,
|
||||
toasts: widget::toaster::Toasts<Message>,
|
||||
watcher_opt: Option<(Debouncer<RecommendedWatcher, FileIdMap>, HashSet<PathBuf>)>,
|
||||
nav_dnd_hover: Option<(Location, Instant)>,
|
||||
tab_dnd_hover: Option<(Entity, Instant)>,
|
||||
|
|
@ -641,7 +650,7 @@ impl App {
|
|||
widget::settings::view_column(children).into()
|
||||
}
|
||||
|
||||
fn operations(&self) -> Element<Message> {
|
||||
fn edit_history(&self) -> Element<Message> {
|
||||
let mut children = Vec::new();
|
||||
|
||||
//TODO: get height from theme?
|
||||
|
|
@ -651,7 +660,7 @@ impl App {
|
|||
let mut section = widget::settings::view_section(fl!("pending"));
|
||||
for (_id, (op, progress)) in self.pending_operations.iter().rev() {
|
||||
section = section.add(widget::column::with_children(vec![
|
||||
widget::text(format!("{:?}", op)).into(),
|
||||
widget::text(op.pending_text()).into(),
|
||||
widget::progress_bar(0.0..=100.0, *progress)
|
||||
.height(progress_bar_height)
|
||||
.into(),
|
||||
|
|
@ -664,7 +673,7 @@ impl App {
|
|||
let mut section = widget::settings::view_section(fl!("failed"));
|
||||
for (_id, (op, error)) in self.failed_operations.iter().rev() {
|
||||
section = section.add(widget::column::with_children(vec![
|
||||
widget::text(format!("{:?}", op)).into(),
|
||||
widget::text(op.pending_text()).into(),
|
||||
widget::text(error).into(),
|
||||
]));
|
||||
}
|
||||
|
|
@ -674,11 +683,15 @@ impl App {
|
|||
if !self.complete_operations.is_empty() {
|
||||
let mut section = widget::settings::view_section(fl!("complete"));
|
||||
for (_id, op) in self.complete_operations.iter().rev() {
|
||||
section = section.add(widget::text(format!("{:?}", op)));
|
||||
section = section.add(widget::text(op.completed_text()));
|
||||
}
|
||||
children.push(section.into());
|
||||
}
|
||||
|
||||
if children.is_empty() {
|
||||
children.push(widget::text::body(fl!("no-history")).into());
|
||||
}
|
||||
|
||||
widget::settings::view_column(children).into()
|
||||
}
|
||||
|
||||
|
|
@ -978,6 +991,7 @@ impl Application for App {
|
|||
search_active: false,
|
||||
search_id: widget::Id::unique(),
|
||||
search_input: String::new(),
|
||||
toasts: widget::toaster::Toasts::default(),
|
||||
watcher_opt: None,
|
||||
nav_dnd_hover: None,
|
||||
tab_dnd_hover: None,
|
||||
|
|
@ -1528,11 +1542,27 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
Message::PendingComplete(id) => {
|
||||
let mut commands = Vec::with_capacity(2);
|
||||
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
||||
if let Some(description) = op.toast() {
|
||||
commands.push(
|
||||
self.toasts.push(
|
||||
widget::toaster::Toast::new(description)
|
||||
/*TODO
|
||||
.action(widget::toaster::ToastAction {
|
||||
description: fl!("undo"),
|
||||
message: Message::Undo(id),
|
||||
})
|
||||
*/
|
||||
.duration(widget::toaster::ToastDuration::Long),
|
||||
),
|
||||
);
|
||||
}
|
||||
self.complete_operations.insert(id, op);
|
||||
}
|
||||
// Manually rescan any trash tabs after any operation is completed
|
||||
return self.rescan_trash();
|
||||
commands.push(self.rescan_trash());
|
||||
return Command::batch(commands);
|
||||
}
|
||||
Message::PendingError(id, err) => {
|
||||
if let Some((op, _)) = self.pending_operations.remove(&id) {
|
||||
|
|
@ -1867,6 +1897,9 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
//TODO: TABRELOAD
|
||||
Message::Toast(toast_message) => {
|
||||
self.toasts.handle_message(&toast_message);
|
||||
}
|
||||
Message::ToggleContextPage(context_page) => {
|
||||
//TODO: ensure context menus are closed
|
||||
if self.context_page == context_page {
|
||||
|
|
@ -1877,6 +1910,9 @@ impl Application for App {
|
|||
}
|
||||
self.set_context_title(context_page.title());
|
||||
}
|
||||
Message::Undo(id) => {
|
||||
log::error!("TODO: Undo {id}");
|
||||
}
|
||||
Message::WindowClose => {
|
||||
return window::close(window::Id::MAIN);
|
||||
}
|
||||
|
|
@ -2080,8 +2116,8 @@ impl Application for App {
|
|||
|
||||
Some(match self.context_page {
|
||||
ContextPage::About => self.about(),
|
||||
ContextPage::EditHistory => self.edit_history(),
|
||||
ContextPage::OpenWith => self.open_with(),
|
||||
ContextPage::Operations => self.operations(),
|
||||
ContextPage::Properties(entity) => self.properties(entity),
|
||||
ContextPage::Settings => self.settings(),
|
||||
})
|
||||
|
|
@ -2320,19 +2356,35 @@ impl Application for App {
|
|||
}
|
||||
|
||||
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
||||
vec![if self.search_active {
|
||||
widget::text_input::search_input("", &self.search_input)
|
||||
.width(Length::Fixed(240.0))
|
||||
.id(self.search_id.clone())
|
||||
.on_clear(Message::SearchClear)
|
||||
.on_input(Message::SearchInput)
|
||||
.on_submit(Message::SearchSubmit)
|
||||
.into()
|
||||
let mut elements = Vec::with_capacity(2);
|
||||
|
||||
if !self.pending_operations.is_empty() {
|
||||
elements.push(
|
||||
widget::button::text(format!("{}", self.pending_operations.len()))
|
||||
.on_press(Message::ToggleContextPage(ContextPage::EditHistory))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
if self.search_active {
|
||||
elements.push(
|
||||
widget::text_input::search_input("", &self.search_input)
|
||||
.width(Length::Fixed(240.0))
|
||||
.id(self.search_id.clone())
|
||||
.on_clear(Message::SearchClear)
|
||||
.on_input(Message::SearchInput)
|
||||
.on_submit(Message::SearchSubmit)
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
widget::button::icon(widget::icon::from_name("system-search-symbolic"))
|
||||
.on_press(Message::SearchActivate)
|
||||
.into()
|
||||
}]
|
||||
elements.push(
|
||||
widget::button::icon(widget::icon::from_name("system-search-symbolic"))
|
||||
.on_press(Message::SearchActivate)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
elements
|
||||
}
|
||||
|
||||
/// Creates a view after each update.
|
||||
|
|
@ -2374,7 +2426,7 @@ impl Application for App {
|
|||
}
|
||||
}
|
||||
|
||||
let content: Element<_> = tab_column.into();
|
||||
let content: Element<_> = widget::toaster::toaster(&self.toasts, tab_column).into();
|
||||
|
||||
// Uncomment to debug layout:
|
||||
//content.explain(cosmic::iced::Color::WHITE)
|
||||
|
|
@ -2440,7 +2492,7 @@ impl Application for App {
|
|||
move |events_res: notify_debouncer_full::DebounceEventResult| {
|
||||
match events_res {
|
||||
Ok(mut events) => {
|
||||
eprintln!("{:?}", events);
|
||||
log::debug!("{:?}", events);
|
||||
|
||||
events.retain(|event| {
|
||||
match &event.kind {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use i18n_embed::{
|
|||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
};
|
||||
use icu::collator::{Collator, CollatorOptions, Numeric};
|
||||
use icu_collator::{Collator, CollatorOptions, Numeric};
|
||||
use icu_provider::DataLocale;
|
||||
use once_cell::sync::Lazy;
|
||||
use rust_embed::RustEmbed;
|
||||
|
|
@ -40,6 +40,19 @@ pub static LANGUAGE_SORTER: Lazy<Collator> = Lazy::new(|| {
|
|||
.expect("Creating a collator from the system's current language, the fallback language, or American English should succeed")
|
||||
});
|
||||
|
||||
pub static LANGUAGE_CHRONO: Lazy<chrono::Locale> = Lazy::new(|| {
|
||||
std::env::var("LANG")
|
||||
.ok()
|
||||
.and_then(|locale_full| {
|
||||
// Split LANG because it may be set to a locale such as en_US.UTF8
|
||||
locale_full
|
||||
.split('.')
|
||||
.next()
|
||||
.and_then(|locale| chrono::Locale::from_str(locale).ok())
|
||||
})
|
||||
.unwrap_or(chrono::Locale::en_US)
|
||||
});
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fl {
|
||||
($message_id:literal) => {{
|
||||
|
|
|
|||
|
|
@ -212,8 +212,7 @@ pub fn menu_bar<'a>(
|
|||
menu::Item::Button(fl!("paste"), Action::Paste),
|
||||
menu::Item::Button(fl!("select-all"), Action::SelectAll),
|
||||
menu::Item::Divider,
|
||||
//TODO: edit history
|
||||
menu::Item::Button(fl!("operations"), Action::Operations),
|
||||
menu::Item::Button(fl!("history"), Action::EditHistory),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
183
src/operation.rs
183
src/operation.rs
|
|
@ -1,5 +1,6 @@
|
|||
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
|
|
@ -197,7 +198,121 @@ fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
fn file_name<'a>(path: &'a Path) -> Cow<'a, str> {
|
||||
path.file_name()
|
||||
.map_or_else(|| fl!("unknown-folder").into(), |x| x.to_string_lossy())
|
||||
}
|
||||
|
||||
fn parent_name<'a>(path: &'a Path) -> Cow<'a, str> {
|
||||
let Some(parent) = path.parent() else {
|
||||
return fl!("unknown-folder").into();
|
||||
};
|
||||
|
||||
file_name(parent)
|
||||
}
|
||||
|
||||
fn paths_parent_name<'a>(paths: &'a Vec<PathBuf>) -> Cow<'a, str> {
|
||||
let Some(first_path) = paths.first() else {
|
||||
return fl!("unknown-folder").into();
|
||||
};
|
||||
|
||||
let Some(parent) = first_path.parent() else {
|
||||
return fl!("unknown-folder").into();
|
||||
};
|
||||
|
||||
for path in paths.iter() {
|
||||
//TODO: is it possible to have different parents, and what should be returned?
|
||||
if path.parent() != Some(parent) {
|
||||
return fl!("unknown-folder").into();
|
||||
}
|
||||
}
|
||||
|
||||
file_name(parent)
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn pending_text(&self) -> String {
|
||||
match self {
|
||||
Self::Copy { paths, to } => fl!(
|
||||
"copying",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = file_name(to)
|
||||
),
|
||||
Self::Delete { paths } => fl!(
|
||||
"moving",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = fl!("trash")
|
||||
),
|
||||
Self::EmptyTrash => fl!("emptying-trash"),
|
||||
Self::Move { paths, to } => fl!(
|
||||
"moving",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = file_name(to)
|
||||
),
|
||||
Self::NewFile { path } => fl!(
|
||||
"creating",
|
||||
name = file_name(path),
|
||||
parent = parent_name(path)
|
||||
),
|
||||
Self::NewFolder { path } => fl!(
|
||||
"creating",
|
||||
name = file_name(path),
|
||||
parent = parent_name(path)
|
||||
),
|
||||
Self::Rename { from, to } => {
|
||||
fl!("renaming", from = file_name(from), to = file_name(to))
|
||||
}
|
||||
Self::Restore { paths } => fl!("restoring", items = paths.len()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn completed_text(&self) -> String {
|
||||
match self {
|
||||
Self::Copy { paths, to } => fl!(
|
||||
"copied",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = file_name(to)
|
||||
),
|
||||
Self::Delete { paths } => fl!(
|
||||
"moved",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = fl!("trash")
|
||||
),
|
||||
Self::EmptyTrash => fl!("emptied-trash"),
|
||||
Self::Move { paths, to } => fl!(
|
||||
"moved",
|
||||
items = paths.len(),
|
||||
from = paths_parent_name(paths),
|
||||
to = file_name(to)
|
||||
),
|
||||
Self::NewFile { path } => fl!(
|
||||
"created",
|
||||
name = file_name(path),
|
||||
parent = parent_name(path)
|
||||
),
|
||||
Self::NewFolder { path } => fl!(
|
||||
"created",
|
||||
name = file_name(path),
|
||||
parent = parent_name(path)
|
||||
),
|
||||
Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)),
|
||||
Self::Restore { paths } => fl!("restored", items = paths.len()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toast(&self) -> Option<String> {
|
||||
match self {
|
||||
Self::Delete { .. } => Some(self.completed_text()),
|
||||
//TODO: more toasts
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform the operation
|
||||
pub async fn perform(
|
||||
self,
|
||||
|
|
@ -242,38 +357,34 @@ impl Operation {
|
|||
let msg_tx = msg_tx.clone();
|
||||
tokio::task::spawn_blocking(move || -> fs_extra::error::Result<()> {
|
||||
log::info!("Copy {:?} to {:?}", paths, to);
|
||||
let copied_bytes = AtomicU64::default();
|
||||
let total_bytes = paths
|
||||
.iter()
|
||||
.map(fs_extra::dir::get_size)
|
||||
.sum::<Result<u64, _>>()?;
|
||||
let handler = || {
|
||||
executor::block_on(async {
|
||||
let _ = msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::PendingProgress(
|
||||
id,
|
||||
100.0 * copied_bytes.load(atomic::Ordering::Relaxed) as f32
|
||||
/ total_bytes as f32,
|
||||
))
|
||||
.await;
|
||||
})
|
||||
};
|
||||
// Files and directory progress are handled separately
|
||||
let file_handler = |progress: fs_extra::file::TransitProcess| {
|
||||
copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed);
|
||||
handler();
|
||||
};
|
||||
let dir_handler = |progress: fs_extra::TransitProcess| {
|
||||
copied_bytes.fetch_add(progress.copied_bytes, atomic::Ordering::Relaxed);
|
||||
handler();
|
||||
handle_progress_state(&msg_tx, &progress)
|
||||
};
|
||||
for (from, mut to) in paths.into_iter().zip(to.into_iter()) {
|
||||
let total_paths = paths.len();
|
||||
for (path_i, (from, mut to)) in
|
||||
paths.into_iter().zip(to.into_iter()).enumerate()
|
||||
{
|
||||
let handler = |copied_bytes, total_bytes| {
|
||||
let item_progress = copied_bytes as f32 / total_bytes as f32;
|
||||
let total_progress =
|
||||
(item_progress + path_i as f32) / total_paths as f32;
|
||||
executor::block_on(async {
|
||||
let _ = msg_tx
|
||||
.lock()
|
||||
.await
|
||||
.send(Message::PendingProgress(id, 100.0 * total_progress))
|
||||
.await;
|
||||
})
|
||||
};
|
||||
|
||||
if from.is_dir() {
|
||||
let options = fs_extra::dir::CopyOptions::default().copy_inside(true);
|
||||
fs_extra::copy_items_with_progress(&[from], to, &options, dir_handler)?;
|
||||
fs_extra::copy_items_with_progress(
|
||||
&[from],
|
||||
to,
|
||||
&options,
|
||||
|progress: fs_extra::TransitProcess| {
|
||||
handler(progress.copied_bytes, progress.total_bytes);
|
||||
handle_progress_state(&msg_tx, &progress)
|
||||
},
|
||||
)?;
|
||||
} else {
|
||||
let mut options = fs_extra::file::CopyOptions::default();
|
||||
if to.exists() {
|
||||
|
|
@ -301,7 +412,14 @@ impl Operation {
|
|||
}
|
||||
}
|
||||
}
|
||||
fs_extra::file::copy_with_progress(from, to, &options, file_handler)?;
|
||||
fs_extra::file::copy_with_progress(
|
||||
from,
|
||||
to,
|
||||
&options,
|
||||
|progress: fs_extra::file::TransitProcess| {
|
||||
handler(progress.copied_bytes, progress.total_bytes);
|
||||
},
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -314,10 +432,11 @@ impl Operation {
|
|||
let total = paths.len();
|
||||
let mut count = 0;
|
||||
for path in paths {
|
||||
tokio::task::spawn_blocking(|| trash::delete(path))
|
||||
let items_opt = tokio::task::spawn_blocking(|| trash::delete(path))
|
||||
.await
|
||||
.map_err(err_str)?
|
||||
.map_err(err_str)?;
|
||||
//TODO: items_opt allows for easy restore
|
||||
count += 1;
|
||||
let _ = msg_tx
|
||||
.lock()
|
||||
|
|
|
|||
14
src/tab.rs
14
src/tab.rs
|
|
@ -808,21 +808,24 @@ impl Item {
|
|||
if let Ok(time) = metadata.created() {
|
||||
column = column.push(widget::text(format!(
|
||||
"Created: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format(TIME_FORMAT)
|
||||
chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO)
|
||||
)));
|
||||
}
|
||||
|
||||
if let Ok(time) = metadata.modified() {
|
||||
column = column.push(widget::text(format!(
|
||||
"Modified: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format(TIME_FORMAT)
|
||||
chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO)
|
||||
)));
|
||||
}
|
||||
|
||||
if let Ok(time) = metadata.accessed() {
|
||||
column = column.push(widget::text(format!(
|
||||
"Accessed: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format(TIME_FORMAT)
|
||||
chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -862,7 +865,8 @@ impl Item {
|
|||
if let Ok(time) = metadata.modified() {
|
||||
column = column.push(widget::text(format!(
|
||||
"Last modified: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format(TIME_FORMAT)
|
||||
chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
|
@ -2569,7 +2573,7 @@ impl Tab {
|
|||
let modified_text = match &item.metadata {
|
||||
ItemMetadata::Path { metadata, .. } => match metadata.modified() {
|
||||
Ok(time) => chrono::DateTime::<chrono::Local>::from(time)
|
||||
.format(TIME_FORMAT)
|
||||
.format_localized(TIME_FORMAT, *LANGUAGE_CHRONO)
|
||||
.to_string(),
|
||||
Err(_) => String::new(),
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue