Files
Ely-GPUI-Components/examples/gallery/pages/files/actions.rs
T
ZacharyZhang-NY 14de69fed6 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.
2026-09-26 20:07:33 -04:00

121 lines
4.1 KiB
Rust

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),
))
}