Files · Explorer: a folder as a list, tiles or columns, with one path and one order
FileExplorer shows the folder at the end of a path three ways: the directory listing, a grid of tiles, or a column per folder along the path with the next folder lit. Its name, its count and a switch of views sit above one path bar that climbs. A folder opens in place; a file goes to the host, which lists each folder it goes to. A pick in an earlier column closes the columns past it, and a new deepest column scrolls into view. The directory listing takes its pick from its owner and reports it, so the pick carries across views; its path bar and its first order are shared. The gallery walks a small file system and shows the tree, the context menu, renaming in place and a drop zone in use. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
906e6c8ac1
commit
f694f0d413
17 files changed
+991
-41
No files matched your search
@@ -0,0 +1,120 @@
|
||||
use ely_gpui_component::{
|
||||
files::{FileGrid, FileIcon, FileItem},
|
||||
forms::{DropZone, InlineEdit},
|
||||
lists::{DirEntry, SelectableList},
|
||||
menus::{ContextMenu, Menu, MenuItem},
|
||||
primitives::IconName,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use jiff::Timestamp;
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{change, keep, section},
|
||||
};
|
||||
|
||||
pub fn context_menu(cx: &mut App) -> impl IntoElement + use<> {
|
||||
let item = |label: &'static str, icon: IconName| {
|
||||
MenuItem::new(label)
|
||||
.icon(icon)
|
||||
.on_click(move |_, _| log::info!("gallery: {label}"))
|
||||
};
|
||||
let menu = Menu::new()
|
||||
.item(item("Open", IconName::FolderOpen))
|
||||
.item(item("Rename", IconName::PenLine))
|
||||
.item(item("Duplicate", IconName::Copy))
|
||||
.separator()
|
||||
.item(item("Move to Trash", IconName::Trash2));
|
||||
section(
|
||||
"FileContextMenu",
|
||||
"What a right press on files offers, as menus::ContextMenu draws it: open, rename, duplicate, and the trash apart.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"files-context",
|
||||
div().w(px(640.)).child(
|
||||
ContextMenu::new("files-context", menu)
|
||||
.child(FileGrid::new("files-context-grid", super::entries().into_iter().take(6))),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn rename(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let name = keep(
|
||||
"files-rename",
|
||||
|| SharedString::from("Brief.md"),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let now = name.read(cx).clone();
|
||||
section(
|
||||
"RenameInline",
|
||||
"A name edited in place, as forms::InlineEdit does it: a press or Enter edits, Enter keeps and Escape drops; the icon follows the new name.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"files-rename",
|
||||
div()
|
||||
.w(px(320.))
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_2()
|
||||
.child(FileIcon::file(&now))
|
||||
.child(div().flex_1().min_w_0().child(
|
||||
InlineEdit::new("files-rename", now).on_commit(move |text, _, cx| {
|
||||
change(&name, cx, |name| *name = text.clone())
|
||||
}),
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn drop_files(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let dropped = keep("files-dropped", Vec::<DirEntry>::new, window, cx);
|
||||
let now = dropped.read(cx).clone();
|
||||
let take = dropped.clone();
|
||||
let list = now
|
||||
.iter()
|
||||
.fold(SelectableList::new("files-dropped"), |list, entry| {
|
||||
let key = entry.name().clone();
|
||||
let id = SharedString::from(format!("files-dropped-{key}"));
|
||||
list.row(key, FileItem::new(id, entry.clone()).into())
|
||||
});
|
||||
section(
|
||||
"DragDropFiles",
|
||||
"Files dropped in, as forms::DropZone takes them; each lands in the list below.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"files-drop",
|
||||
div()
|
||||
.w(px(420.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.child(
|
||||
DropZone::new("files-drop")
|
||||
.multiple()
|
||||
.hint("Any kind of file")
|
||||
.on_drop(move |paths, _, cx| {
|
||||
let entries = paths.iter().map(|path| {
|
||||
let bytes = path.metadata().expect("a dropped file reads").len();
|
||||
let name = path.file_name().expect("a file name");
|
||||
DirEntry::file(
|
||||
name.to_string_lossy().to_string(),
|
||||
bytes,
|
||||
Timestamp::now(),
|
||||
)
|
||||
});
|
||||
let entries: Vec<DirEntry> = entries.collect();
|
||||
change(&take, cx, |dropped| {
|
||||
for entry in entries {
|
||||
if !dropped.iter().any(|kept| kept.name() == entry.name()) {
|
||||
dropped.push(entry);
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
.child(list),
|
||||
))
|
||||
}
|
||||
Reference in new issue
Block a user