Files: a narrow listing keeps its names; a column starts fresh for a new folder
The directory listing's size and date columns are fixed, so at 280px the name had no room and rows showed no names. The heading and rows now sit in a sideways scroll box whose child keeps the least width: the name's least, the fixed columns and each row's icon, gaps and padding. A column of the explorer was keyed by its depth, so a new folder there inherited the old list's cursor; columns and rows are keyed by their folder. The explorer asserts each folder on its path is a folder in its parent.
This commit is contained in:
1 parent
14de69fed6
commit
4717aad596
7 files changed
+169
-31
No files matched your search
@@ -43,11 +43,12 @@ pub(super) fn columns(
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(depth, entries)| {
|
||||
let folder = path[..=depth].join("/");
|
||||
let rows = entries.iter().enumerate().fold(
|
||||
SelectableList::new((id.clone(), format!("column-{depth}"))),
|
||||
SelectableList::new((id.clone(), format!("column-{folder}"))),
|
||||
|list, (ix, entry)| {
|
||||
let row = ListItem::new(
|
||||
(id.clone(), format!("row-{depth}-{ix}")),
|
||||
(id.clone(), format!("row-{folder}-{ix}")),
|
||||
entry.name().clone(),
|
||||
)
|
||||
.leading(FileIcon::entry(entry))
|
||||
|
||||
+10
-1
@@ -63,6 +63,15 @@ impl FileExplorer {
|
||||
path.len(),
|
||||
levels.len()
|
||||
);
|
||||
for (depth, name) in path.iter().enumerate().skip(1) {
|
||||
assert!(
|
||||
levels[depth - 1]
|
||||
.iter()
|
||||
.any(|entry| entry.is_folder() && entry.name() == name),
|
||||
"{name} is no folder in {}",
|
||||
path[depth - 1]
|
||||
);
|
||||
}
|
||||
Self {
|
||||
id: id.into(),
|
||||
path,
|
||||
@@ -93,7 +102,7 @@ impl FileExplorer {
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets the path to go to, from the root down.
|
||||
/// Gets the path to go to, from the root down; the pick stays with the folder left, so the host drops it.
|
||||
pub fn on_navigate(
|
||||
mut self,
|
||||
handler: impl Fn(&[SharedString], &mut Window, &mut App) + 'static,
|
||||
|
||||
@@ -174,3 +174,83 @@ fn the_pick_is_in_the_folder_shown() {
|
||||
let here = vec![DirEntry::file("Brief.md", 10, then())];
|
||||
let _ = FileExplorer::new("explorer", ["Home"], [here], FileView::List).selected("Ghost.md");
|
||||
}
|
||||
|
||||
/// An explorer that goes where it is sent, over a small tree, and the files it opened.
|
||||
struct Walking {
|
||||
path: Vec<SharedString>,
|
||||
opened: Vec<SharedString>,
|
||||
}
|
||||
|
||||
fn folder(path: &[SharedString]) -> Vec<DirEntry> {
|
||||
let names: Vec<&str> = path.iter().map(|level| level.as_ref()).collect();
|
||||
match names.join("/").as_str() {
|
||||
"Home" => vec![
|
||||
DirEntry::folder("Projects", then()),
|
||||
DirEntry::folder("Documents", then()),
|
||||
],
|
||||
"Home/Projects" => vec![
|
||||
DirEntry::folder("Atrium", then()),
|
||||
DirEntry::folder("Dunes", then()),
|
||||
],
|
||||
"Home/Projects/Dunes" => vec![DirEntry::file("Survey.csv", 10, then())],
|
||||
"Home/Documents" => vec![
|
||||
DirEntry::file("Contract.docx", 10, then()),
|
||||
DirEntry::file("Invoice.pdf", 10, then()),
|
||||
],
|
||||
other => panic!("no folder {other}"),
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Walking {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let (go, open) = (cx.entity(), cx.entity());
|
||||
let levels = (1..=self.path.len()).map(|depth| folder(&self.path[..depth]));
|
||||
let explorer = FileExplorer::new("explorer", self.path.clone(), levels, FileView::Columns)
|
||||
.on_navigate(move |path, _, cx| {
|
||||
go.update(cx, |host, cx| {
|
||||
host.path = path.to_vec();
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_open(move |entry, _, cx| {
|
||||
open.update(cx, |host, _| host.opened.push(entry.name().clone()))
|
||||
});
|
||||
div().w(px(640.0)).child(explorer)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_new_folder_at_a_depth_starts_its_column_fresh(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (host, cx) = cx.add_window_view(|_, _| Walking {
|
||||
path: ["Home", "Projects"].map(SharedString::from).to_vec(),
|
||||
opened: Vec::new(),
|
||||
});
|
||||
cx.update(|window, _| window.activate_window());
|
||||
settle(cx);
|
||||
tab_to(6, cx);
|
||||
press("down", cx);
|
||||
host.update(cx, |host, cx| {
|
||||
host.path = ["Home", "Documents"].map(SharedString::from).to_vec();
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
tab_to(6, cx);
|
||||
press("enter", cx);
|
||||
assert_eq!(
|
||||
host.read_with(cx, |host, _| host.opened.clone()),
|
||||
["Contract.docx"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Projects is no folder in Home")]
|
||||
fn each_folder_on_the_path_is_listed_in_its_parent() {
|
||||
let root = vec![DirEntry::file("Projects", 10, then())];
|
||||
let _ = FileExplorer::new(
|
||||
"explorer",
|
||||
["Home", "Projects"],
|
||||
[root, Vec::new()],
|
||||
FileView::List,
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user