Files · Find and look: preview, recent files, search, duplicates and archives
FilePreview shows a file large: its picture in the host's shape, the first lines of its text, or its kind's icon, over its name, kind, size and change; the gallery opens it on Space as a quick look. RecentFiles lists files newest first under a header a day, a list a day. FileSearch sits over a field the host owns and lists what the host found with its folder. DuplicateFinder marks copies to remove and keeps the last of each group, its mark shut, with the bytes the marks free beside Remove. ArchiveViewer draws an archive's entries as a tree with sizes and savings, from paths gathered by the folder builder the file tree now shares. A checkbox's label wraps inside its box instead of running past it. Favorites and the hex viewer point to documents and debug.
This commit is contained in:
1 parent
4717aad596
commit
d76a489934
17 files changed
+1507
-22
No files matched your search
@@ -0,0 +1,131 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
App, ElementId, FontWeight, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
|
||||
Window, div, prelude::*,
|
||||
};
|
||||
use jiff::{Timestamp, civil::Date, tz::TimeZone};
|
||||
|
||||
use super::FileIcon;
|
||||
use crate::{
|
||||
lists::{ListItem, SelectableList},
|
||||
theme::{ActiveTheme, TextSize},
|
||||
typography::format::{datetime, system_zone},
|
||||
};
|
||||
|
||||
type OnRecent = Rc<dyn Fn(&RecentFile, &mut Window, &mut App)>;
|
||||
|
||||
/// A file opened lately: its name, the folder it lives in, and when it was opened.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RecentFile {
|
||||
pub name: SharedString,
|
||||
pub folder: SharedString,
|
||||
pub opened: Timestamp,
|
||||
}
|
||||
|
||||
/// The header over a day's files: Today, Yesterday, or the weekday and date.
|
||||
pub(crate) fn day_label(day: Date, today: Date) -> String {
|
||||
if day == today {
|
||||
"Today".into()
|
||||
} else if today.yesterday().ok() == Some(day) {
|
||||
"Yesterday".into()
|
||||
} else {
|
||||
day.strftime("%A, %B %-d").to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Files opened lately, newest first, under a header for each day: Today, Yesterday, then the weekday and date. Each day is a list; Tab moves between days, and Enter or a double press opens a file.
|
||||
#[derive(IntoElement)]
|
||||
pub struct RecentFiles {
|
||||
id: ElementId,
|
||||
files: Vec<RecentFile>,
|
||||
zone: Option<TimeZone>,
|
||||
on_open: Option<OnRecent>,
|
||||
}
|
||||
|
||||
impl RecentFiles {
|
||||
pub fn new(id: impl Into<ElementId>, files: impl IntoIterator<Item = RecentFile>) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
files: files.into_iter().collect(),
|
||||
zone: None,
|
||||
on_open: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The zone days turn in; the system's by default.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_open(
|
||||
mut self,
|
||||
handler: impl Fn(&RecentFile, &mut Window, &mut App) + 'static,
|
||||
) -> Self {
|
||||
self.on_open = Some(Rc::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for RecentFiles {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self.zone.unwrap_or_else(|| system_zone("recent files"));
|
||||
let today = Timestamp::now().to_zoned(zone.clone()).date();
|
||||
let mut files = self.files;
|
||||
files.sort_by_key(|file| std::cmp::Reverse(file.opened));
|
||||
let files: Rc<[RecentFile]> = files.into();
|
||||
let theme = cx.theme();
|
||||
let colors = &theme.colors;
|
||||
let mut days: Vec<(Date, Vec<usize>)> = Vec::new();
|
||||
for (ix, file) in files.iter().enumerate() {
|
||||
let day = file.opened.to_zoned(zone.clone()).date();
|
||||
match days.last_mut() {
|
||||
Some((last, those)) if *last == day => those.push(ix),
|
||||
_ => days.push((day, vec![ix])),
|
||||
}
|
||||
}
|
||||
let groups = days.into_iter().map(|(day, those)| {
|
||||
let list = those.iter().fold(
|
||||
SelectableList::new((self.id.clone(), format!("day-{day}"))),
|
||||
|list, ix| {
|
||||
let file = &files[*ix];
|
||||
let time = datetime(file.opened, &zone, "%H:%M").expect("a time formats");
|
||||
let row =
|
||||
ListItem::new((self.id.clone(), format!("recent-{ix}")), file.name.clone())
|
||||
.leading(FileIcon::file(&file.name))
|
||||
.description(file.folder.clone())
|
||||
.trailing(time);
|
||||
list.row(SharedString::from(ix.to_string()), row)
|
||||
},
|
||||
);
|
||||
let (id, files, on_open) = (self.id.clone(), files.clone(), self.on_open.clone());
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.px_3()
|
||||
.text_size(theme.text_size(TextSize::Sm))
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
.text_color(colors.fg_muted)
|
||||
.child(day_label(day, today)),
|
||||
)
|
||||
.child(list.on_activate(move |key, window, cx| {
|
||||
let file = &files[key.parse::<usize>().expect("a row's place")];
|
||||
log::info!("recent files {id:?}: open {}", file.name);
|
||||
if let Some(on_open) = &on_open {
|
||||
on_open(file, window, cx);
|
||||
}
|
||||
}))
|
||||
});
|
||||
div()
|
||||
.debug_selector(|| "recent-files".into())
|
||||
.w_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_4()
|
||||
.children(groups)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user