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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android_system_properties"
|
name = "android_system_properties"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
|
@ -705,6 +711,21 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
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]]
|
[[package]]
|
||||||
name = "clipboard-win"
|
name = "clipboard-win"
|
||||||
version = "4.5.0"
|
version = "4.5.0"
|
||||||
|
|
@ -945,6 +966,7 @@ dependencies = [
|
||||||
name = "cosmic-files"
|
name = "cosmic-files"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fork",
|
"fork",
|
||||||
|
|
@ -2270,6 +2292,29 @@ dependencies = [
|
||||||
"syn 2.0.48",
|
"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]]
|
[[package]]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
|
|
@ -3930,6 +3975,12 @@ version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e"
|
checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pure-rust-locales"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed02a829e62dc2715ceb8afb4f80e298148e1345749ceb369540fe0eb3368432"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = { version = "0.4", features = ["unstable-locales"] }
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
lazy_static = "1"
|
lazy_static = "1"
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ empty-folder-hidden = Empty folder (has hidden items)
|
||||||
|
|
||||||
# Context Pages
|
# Context Pages
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
properties = Properties
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
settings = Settings
|
settings = Settings
|
||||||
|
|
||||||
|
|
@ -19,4 +22,3 @@ new-folder = New folder
|
||||||
copy = Copy
|
copy = Copy
|
||||||
paste = Paste
|
paste = Paste
|
||||||
select-all = Select all
|
select-all = Select all
|
||||||
new-tab = New tab
|
|
||||||
|
|
|
||||||
20
src/main.rs
20
src/main.rs
|
|
@ -103,6 +103,7 @@ pub enum Action {
|
||||||
NewFile,
|
NewFile,
|
||||||
NewFolder,
|
NewFolder,
|
||||||
Paste,
|
Paste,
|
||||||
|
Properties,
|
||||||
SelectAll,
|
SelectAll,
|
||||||
Settings,
|
Settings,
|
||||||
TabNew,
|
TabNew,
|
||||||
|
|
@ -115,6 +116,7 @@ impl Action {
|
||||||
Action::NewFile => Message::NewFile(Some(entity)),
|
Action::NewFile => Message::NewFile(Some(entity)),
|
||||||
Action::NewFolder => Message::NewFolder(Some(entity)),
|
Action::NewFolder => Message::NewFolder(Some(entity)),
|
||||||
Action::Paste => Message::Paste(Some(entity)),
|
Action::Paste => Message::Paste(Some(entity)),
|
||||||
|
Action::Properties => Message::ToggleContextPage(ContextPage::Properties),
|
||||||
Action::SelectAll => Message::SelectAll(Some(entity)),
|
Action::SelectAll => Message::SelectAll(Some(entity)),
|
||||||
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
Action::Settings => Message::ToggleContextPage(ContextPage::Settings),
|
||||||
Action::TabNew => Message::TabNew,
|
Action::TabNew => Message::TabNew,
|
||||||
|
|
@ -146,12 +148,14 @@ pub enum Message {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum ContextPage {
|
pub enum ContextPage {
|
||||||
|
Properties,
|
||||||
Settings,
|
Settings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextPage {
|
impl ContextPage {
|
||||||
fn title(&self) -> String {
|
fn title(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Properties => fl!("properties"),
|
||||||
Self::Settings => fl!("settings"),
|
Self::Settings => fl!("settings"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +235,21 @@ impl App {
|
||||||
self.set_window_title(window_title)
|
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> {
|
fn settings(&self) -> Element<Message> {
|
||||||
let app_theme_selected = match self.config.app_theme {
|
let app_theme_selected = match self.config.app_theme {
|
||||||
AppTheme::Dark => 1,
|
AppTheme::Dark => 1,
|
||||||
|
|
@ -447,6 +466,7 @@ impl Application for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(match self.context_page {
|
Some(match self.context_page {
|
||||||
|
ContextPage::Properties => self.properties(),
|
||||||
ContextPage::Settings => self.settings(),
|
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!("paste"), Action::Paste),
|
||||||
menu_action(fl!("select-all"), Action::SelectAll),
|
menu_action(fl!("select-all"), Action::SelectAll),
|
||||||
horizontal_rule(1),
|
horizontal_rule(1),
|
||||||
menu_action(fl!("new-tab"), Action::TabNew),
|
menu_action(fl!("properties"), Action::Properties),
|
||||||
menu_action(fl!("settings"), Action::Settings),
|
|
||||||
))
|
))
|
||||||
.padding(1)
|
.padding(1)
|
||||||
//TODO: move style to libcosmic
|
//TODO: move style to libcosmic
|
||||||
|
|
|
||||||
131
src/tab.rs
131
src/tab.rs
|
|
@ -10,7 +10,8 @@ use cosmic::{
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fmt, fs,
|
fmt,
|
||||||
|
fs::{self, Metadata},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process,
|
process,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
|
|
@ -92,24 +93,16 @@ fn folder_icon(path: &PathBuf, icon_size: u16) -> widget::icon::Handle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
fn hidden_attribute(_path: &PathBuf) -> bool {
|
fn hidden_attribute(_metadata: &Metadata) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
fn hidden_attribute(path: &PathBuf) -> bool {
|
fn hidden_attribute(metadata: &Metadata) -> bool {
|
||||||
use std::os::windows::fs::MetadataExt;
|
use std::os::windows::fs::MetadataExt;
|
||||||
match fs::metadata(path) {
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||||
Ok(metadata) => {
|
const FILE_ATTRIBUTE_HIDDEN: u32 = 2;
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
metadata.file_attributes() & FILE_ATTRIBUTE_HIDDEN == FILE_ATTRIBUTE_HIDDEN
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|
@ -156,7 +149,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = match entry.file_name().into_string() {
|
let name = match entry.file_name().into_string() {
|
||||||
Ok(some) => some,
|
Ok(ok) => ok,
|
||||||
Err(name_os) => {
|
Err(name_os) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"failed to parse entry in {:?}: {:?} is not valid UTF-8",
|
"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 path = entry.path();
|
||||||
let hidden = name.starts_with(".") || hidden_attribute(&path);
|
|
||||||
let is_dir = path.is_dir();
|
|
||||||
//TODO: configurable size
|
//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_GRID),
|
||||||
folder_icon(&path, ICON_SIZE_LIST),
|
folder_icon(&path, ICON_SIZE_LIST),
|
||||||
|
|
@ -185,9 +191,9 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
|
|
||||||
items.push(Item {
|
items.push(Item {
|
||||||
name,
|
name,
|
||||||
path,
|
metadata,
|
||||||
hidden,
|
hidden,
|
||||||
is_dir,
|
path,
|
||||||
icon_handle_grid,
|
icon_handle_grid,
|
||||||
icon_handle_list,
|
icon_handle_list,
|
||||||
select_time: None,
|
select_time: None,
|
||||||
|
|
@ -198,7 +204,7 @@ pub fn rescan(tab_path: PathBuf) -> Vec<Item> {
|
||||||
log::warn!("failed to read directory {:?}: {}", tab_path, err);
|
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,
|
(true, false) => Ordering::Less,
|
||||||
(false, true) => Ordering::Greater,
|
(false, true) => Ordering::Greater,
|
||||||
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
|
_ => lexical_sort::natural_lexical_cmp(&a.name, &b.name),
|
||||||
|
|
@ -216,22 +222,93 @@ pub enum Message {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Item {
|
pub struct Item {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: PathBuf,
|
pub metadata: Metadata,
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
pub is_dir: bool,
|
pub path: PathBuf,
|
||||||
pub icon_handle_grid: widget::icon::Handle,
|
pub icon_handle_grid: widget::icon::Handle,
|
||||||
pub icon_handle_list: widget::icon::Handle,
|
pub icon_handle_list: widget::icon::Handle,
|
||||||
pub select_time: Option<Instant>,
|
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 {
|
impl fmt::Debug for Item {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("Item")
|
f.debug_struct("Item")
|
||||||
.field("name", &self.name)
|
.field("name", &self.name)
|
||||||
.field("path", &self.path)
|
.field("metadata", &self.metadata)
|
||||||
.field("hidden", &self.hidden)
|
.field("hidden", &self.hidden)
|
||||||
.field("is_dir", &self.is_dir)
|
.field("path", &self.path)
|
||||||
//icon_handles
|
// icon_handles
|
||||||
.field("select_time", &self.select_time)
|
.field("select_time", &self.select_time)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
@ -282,7 +359,7 @@ impl Tab {
|
||||||
if Some(i) == click_i_opt {
|
if Some(i) == click_i_opt {
|
||||||
if let Some(select_time) = item.select_time {
|
if let Some(select_time) = item.select_time {
|
||||||
if select_time.elapsed() < DOUBLE_CLICK_DURATION {
|
if select_time.elapsed() < DOUBLE_CLICK_DURATION {
|
||||||
if item.is_dir {
|
if item.metadata.is_dir() {
|
||||||
cd = Some(item.path.clone());
|
cd = Some(item.path.clone());
|
||||||
} else {
|
} else {
|
||||||
let mut command = open_command(&item.path);
|
let mut command = open_command(&item.path);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue