Add simple file properties
This commit is contained in:
parent
bb5403faf9
commit
42c4ec9dad
6 changed files with 180 additions and 30 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -190,6 +190,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
|
|
@ -705,6 +711,21 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clipboard-win"
|
||||
version = "4.5.0"
|
||||
|
|
@ -945,6 +966,7 @@ dependencies = [
|
|||
name = "cosmic-files"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dirs 5.0.1",
|
||||
"env_logger",
|
||||
"fork",
|
||||
|
|
@ -2270,6 +2292,29 @@ dependencies = [
|
|||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys 0.8.6",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.12.0"
|
||||
|
|
@ -3930,6 +3975,12 @@ version = "1.0.13"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e"
|
||||
|
||||
[[package]]
|
||||
name = "pure-rust-locales"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed02a829e62dc2715ceb8afb4f80e298148e1345749ceb369540fe0eb3368432"
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
chrono = { version = "0.4", features = ["unstable-locales"] }
|
||||
dirs = "5.0.1"
|
||||
env_logger = "0.10"
|
||||
lazy_static = "1"
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ empty-folder-hidden = Empty folder (has hidden items)
|
|||
|
||||
# Context Pages
|
||||
|
||||
## Properties
|
||||
properties = Properties
|
||||
|
||||
## Settings
|
||||
settings = Settings
|
||||
|
||||
|
|
@ -19,4 +22,3 @@ new-folder = New folder
|
|||
copy = Copy
|
||||
paste = Paste
|
||||
select-all = Select all
|
||||
new-tab = New tab
|
||||
|
|
|
|||
20
src/main.rs
20
src/main.rs
|
|
@ -103,6 +103,7 @@ pub enum Action {
|
|||
NewFile,
|
||||
NewFolder,
|
||||
Paste,
|
||||
Properties,
|
||||
SelectAll,
|
||||
Settings,
|
||||
TabNew,
|
||||
|
|
@ -115,6 +116,7 @@ impl Action {
|
|||
Action::NewFile => Message::NewFile(Some(entity)),
|
||||
Action::NewFolder => Message::NewFolder(Some(entity)),
|
||||
Action::Paste => Message::Paste(Some(entity)),
|
||||
Action::Properties => Message::ToggleContextPage(ContextPage::Properties),
|
||||
Action::SelectAll => Message::SelectAll(Some(entity)),
|
||||
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
||||
Action::TabNew => Message::TabNew,
|
||||
|
|
@ -146,12 +148,14 @@ pub enum Message {
|
|||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum ContextPage {
|
||||
Properties,
|
||||
Settings,
|
||||
}
|
||||
|
||||
impl ContextPage {
|
||||
fn title(&self) -> String {
|
||||
match self {
|
||||
Self::Properties => fl!("properties"),
|
||||
Self::Settings => fl!("settings"),
|
||||
}
|
||||
}
|
||||
|
|
@ -231,6 +235,21 @@ impl App {
|
|||
self.set_window_title(window_title)
|
||||
}
|
||||
|
||||
fn properties(&self) -> Element<Message> {
|
||||
let mut children = Vec::new();
|
||||
let entity = self.tab_model.active();
|
||||
if let Some(tab) = self.tab_model.data::<Tab>(entity) {
|
||||
if let Some(ref items) = tab.items_opt {
|
||||
for item in items.iter() {
|
||||
if item.select_time.is_some() {
|
||||
children.push(item.property_view());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
widget::settings::view_column(children).into()
|
||||
}
|
||||
|
||||
fn settings(&self) -> Element<Message> {
|
||||
let app_theme_selected = match self.config.app_theme {
|
||||
AppTheme::Dark => 1,
|
||||
|
|
@ -447,6 +466,7 @@ impl Application for App {
|
|||
}
|
||||
|
||||
Some(match self.context_page {
|
||||
ContextPage::Properties => self.properties(),
|
||||
ContextPage::Settings => self.settings(),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ pub fn context_menu<'a>(entity: segmented_button::Entity) -> Element<'a, Message
|
|||
menu_action(fl!("paste"), Action::Paste),
|
||||
menu_action(fl!("select-all"), Action::SelectAll),
|
||||
horizontal_rule(1),
|
||||
menu_action(fl!("new-tab"), Action::TabNew),
|
||||
menu_action(fl!("settings"), Action::Settings),
|
||||
menu_action(fl!("properties"), Action::Properties),
|
||||
))
|
||||
.padding(1)
|
||||
//TODO: move style to libcosmic
|
||||
|
|
|
|||
131
src/tab.rs
131
src/tab.rs
|
|
@ -10,7 +10,8 @@ use cosmic::{
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::HashMap,
|
||||
fmt, fs,
|
||||
fmt,
|
||||
fs::{self, Metadata},
|
||||
path::PathBuf,
|
||||
process,
|
||||
time::{Duration, Instant},
|
||||
|
|
@ -92,24 +93,16 @@ fn folder_icon(path: &PathBuf, icon_size: u16) -> widget::icon::Handle {
|
|||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn hidden_attribute(_path: &PathBuf) -> bool {
|
||||
fn hidden_attribute(_metadata: &Metadata) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn hidden_attribute(path: &PathBuf) -> bool {
|
||||
fn hidden_attribute(metadata: &Metadata) -> bool {
|
||||
use std::os::windows::fs::MetadataExt;
|
||||
match fs::metadata(path) {
|
||||
Ok(metadata) => {
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
|
||||
metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN
|
||||
}
|
||||
Err(err) => {
|
||||
log::warn!("failed to get hidden attribute for {:?}: {}", path, err);
|
||||
false
|
||||
}
|
||||
}
|
||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
|
||||
metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
@ -156,7 +149,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
|||
};
|
||||
|
||||
let name = match entry.file_name().into_string() {
|
||||
Ok(some) => some,
|
||||
Ok(ok) => ok,
|
||||
Err(name_os) => {
|
||||
log::warn!(
|
||||
"failed to parse entry in {:?}: {:?} is not valid UTF-8",
|
||||
|
|
@ -167,11 +160,24 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
|||
}
|
||||
};
|
||||
|
||||
let metadata = match entry.metadata() {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::warn!(
|
||||
"failed to read metadata for entry in {:?}: {}",
|
||||
tab_path,
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let hidden = name.starts_with(".") || hidden_attribute(&metadata);
|
||||
|
||||
let path = entry.path();
|
||||
let hidden = name.starts_with(".") || hidden_attribute(&path);
|
||||
let is_dir = path.is_dir();
|
||||
|
||||
//TODO: configurable size
|
||||
let (icon_handle_grid, icon_handle_list) = if is_dir {
|
||||
let (icon_handle_grid, icon_handle_list) = if metadata.is_dir() {
|
||||
(
|
||||
folder_icon(&path, ICON_SIZE_GRID),
|
||||
folder_icon(&path, ICON_SIZE_LIST),
|
||||
|
|
@ -185,9 +191,9 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
|||
|
||||
items.push(Item {
|
||||
name,
|
||||
path,
|
||||
metadata,
|
||||
hidden,
|
||||
is_dir,
|
||||
path,
|
||||
icon_handle_grid,
|
||||
icon_handle_list,
|
||||
select_time: None,
|
||||
|
|
@ -198,7 +204,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
|||
log::warn!("failed to read directory {:?}: {}", tab_path, err);
|
||||
}
|
||||
}
|
||||
items.sort_by(|a, b| match (a.is_dir, b.is_dir) {
|
||||
items.sort_by(|a, b| match (a.metadata.is_dir(), b.metadata.is_dir()) {
|
||||
(true, false) => Ordering::Less,
|
||||
(false, true) => Ordering::Greater,
|
||||
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
|
||||
|
|
@ -216,22 +222,93 @@ pub enum Message {
|
|||
#[derive(Clone)]
|
||||
pub struct Item {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub metadata: Metadata,
|
||||
pub hidden: bool,
|
||||
pub is_dir: bool,
|
||||
pub path: PathBuf,
|
||||
pub icon_handle_grid: widget::icon::Handle,
|
||||
pub icon_handle_list: widget::icon::Handle,
|
||||
pub select_time: Option<Instant>,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn property_view(&self) -> Element<crate::Message> {
|
||||
let mut children = Vec::new();
|
||||
children.push(
|
||||
widget::icon::icon(self.icon_handle_grid.clone())
|
||||
.size(ICON_SIZE_GRID)
|
||||
.into(),
|
||||
);
|
||||
children.push(widget::text(self.name.clone()).into());
|
||||
|
||||
//TODO: translate!
|
||||
{
|
||||
const KIB: u64 = 1024;
|
||||
const MIB: u64 = 1024 * KIB;
|
||||
const GIB: u64 = 1024 * MIB;
|
||||
const TIB: u64 = 1024 * GIB;
|
||||
|
||||
fn format_size(size: u64) -> String {
|
||||
if size >= 4 * TIB {
|
||||
format!("{:.1} TiB", size as f64 / TIB as f64)
|
||||
} else if size >= GIB {
|
||||
format!("{:.1} GiB", size as f64 / GIB as f64)
|
||||
} else if size >= MIB {
|
||||
format!("{:.1} MiB", size as f64 / MIB as f64)
|
||||
} else if size >= KIB {
|
||||
format!("{:.1} KiB", size as f64 / KIB as f64)
|
||||
} else {
|
||||
format!("{} B", size)
|
||||
}
|
||||
}
|
||||
|
||||
children
|
||||
.push(widget::text(format!("Size: {}", format_size(self.metadata.len()))).into());
|
||||
}
|
||||
|
||||
if let Ok(time) = self.metadata.accessed() {
|
||||
children.push(
|
||||
widget::text(format!(
|
||||
"Accessed: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format("%c")
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(time) = self.metadata.modified() {
|
||||
children.push(
|
||||
widget::text(format!(
|
||||
"Modified: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format("%c")
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Ok(time) = self.metadata.created() {
|
||||
children.push(
|
||||
widget::text(format!(
|
||||
"Created: {}",
|
||||
chrono::DateTime::<chrono::Local>::from(time).format("%c")
|
||||
))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
widget::column::with_children(children)
|
||||
.width(Length::Fill)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Item {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Item")
|
||||
.field("name", &self.name)
|
||||
.field("path", &self.path)
|
||||
.field("metadata", &self.metadata)
|
||||
.field("hidden", &self.hidden)
|
||||
.field("is_dir", &self.is_dir)
|
||||
//icon_handles
|
||||
.field("path", &self.path)
|
||||
// icon_handles
|
||||
.field("select_time", &self.select_time)
|
||||
.finish()
|
||||
}
|
||||
|
|
@ -282,7 +359,7 @@ impl Tab {
|
|||
if Some(i) == click_i_opt {
|
||||
if let Some(select_time) = item.select_time {
|
||||
if select_time.elapsed() < DOUBLE_CLICK_DURATION {
|
||||
if item.is_dir {
|
||||
if item.metadata.is_dir() {
|
||||
cd = Some(item.path.clone());
|
||||
} else {
|
||||
let mut command = open_command(&item.path);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue