A file on its way is a Transfer with a state: waiting, moving at a rate, paused, done or failed with a reason. One row draws it for the queue and the downloads: its icon, its way, a bar while it moves or waits paused, a status with the time left, and only the actions its state allows. TransferQueue counts what moves and waits. DownloadManager keeps the finished below under Clear, opening on a press and showing their folder. FileOperationProgress tells a copy or move with its bar, time left and current file, its header wrapping by the header rule; a failure speaks in the danger tone. StorageUsage points to data_display's UsageBar.
226 lines
7.2 KiB
Rust
226 lines
7.2 KiB
Rust
use ely_gpui_component::{
|
|
files::{FileGrid, FileIcon, FileItem},
|
|
lists::{DirEntry, DirectoryListing, SelectableList},
|
|
theme::{ActiveTheme, IconSize, TextSize},
|
|
};
|
|
use gpui::{AnyElement, App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
|
use jiff::{Timestamp, ToSpan};
|
|
|
|
mod actions;
|
|
mod explorer;
|
|
mod find;
|
|
mod transfers;
|
|
|
|
use super::Page;
|
|
use crate::{
|
|
probe::probe,
|
|
script::Step,
|
|
ui::{change, keep, section},
|
|
};
|
|
|
|
pub const PAGE: Page = Page {
|
|
number: 27,
|
|
slug: "files",
|
|
title: "Files",
|
|
summary: "Files and folders: their icons, rows and tiles, and the listings, searches and transfers around them.",
|
|
render,
|
|
script: SCRIPT,
|
|
};
|
|
|
|
const SCRIPT: &[Step] = &[
|
|
Step::DownAt("files-grid", 150.0, 40.0),
|
|
Step::UpAt("files-grid", 150.0, 40.0),
|
|
Step::Key("right"),
|
|
Step::Key("down"),
|
|
Step::Wait(200),
|
|
Step::Shot("grid-picked"),
|
|
Step::DownAt("files-explorer", 515.0, 14.0),
|
|
Step::UpAt("files-explorer", 515.0, 14.0),
|
|
Step::Wait(300),
|
|
Step::Shot("explorer-icons"),
|
|
Step::DownAt("files-explorer", 48.0, 120.0),
|
|
Step::UpAt("files-explorer", 48.0, 120.0),
|
|
Step::Key("enter"),
|
|
Step::Wait(300),
|
|
Step::Shot("explorer-into"),
|
|
Step::RightAt("files-context", 150.0, 50.0),
|
|
Step::Wait(300),
|
|
Step::Shot("context-menu"),
|
|
Step::Key("escape"),
|
|
Step::DownAt("files-rename", 80.0, 14.0),
|
|
Step::UpAt("files-rename", 80.0, 14.0),
|
|
Step::Type("Brief notes.md"),
|
|
Step::Key("enter"),
|
|
Step::Wait(300),
|
|
Step::Shot("renamed"),
|
|
Step::DownAt("files-quick", 150.0, 40.0),
|
|
Step::UpAt("files-quick", 150.0, 40.0),
|
|
Step::Key("space"),
|
|
Step::Wait(300),
|
|
Step::Shot("quick-look"),
|
|
Step::Key("escape"),
|
|
Step::DownAt("files-search", 100.0, 16.0),
|
|
Step::UpAt("files-search", 100.0, 16.0),
|
|
Step::Type("a"),
|
|
Step::Wait(300),
|
|
Step::Shot("searched"),
|
|
];
|
|
|
|
/// A project's entries, changed a few hours to a few weeks ago.
|
|
pub fn entries() -> Vec<DirEntry> {
|
|
let now = Timestamp::now();
|
|
let ago = |hours: i64| now - hours.hours();
|
|
vec![
|
|
DirEntry::folder("Design", ago(3)),
|
|
DirEntry::folder("Recordings", ago(30)),
|
|
DirEntry::file("Brief.md", 4_800, ago(2)),
|
|
DirEntry::file("Budget.csv", 18_300, ago(26)),
|
|
DirEntry::file("Atrium.jpg", 2_140_000, ago(50)),
|
|
DirEntry::file("Walkthrough.mov", 184_000_000, ago(120)),
|
|
DirEntry::file("main.rs", 9_200, ago(1)),
|
|
DirEntry::file("Archive.zip", 48_000_000, ago(700)),
|
|
DirEntry::file(
|
|
"Quarterly review with the design team, final.pdf",
|
|
820_000,
|
|
ago(6),
|
|
),
|
|
]
|
|
}
|
|
|
|
fn icons(cx: &mut App) -> impl IntoElement + use<> {
|
|
let theme = cx.theme();
|
|
let (small, muted) = (theme.text_size(TextSize::Xs), theme.colors.fg_muted);
|
|
let names = [
|
|
"Brief.md",
|
|
"main.rs",
|
|
"build.sh",
|
|
"config.toml",
|
|
"data.json",
|
|
"Atrium.jpg",
|
|
"Theme.mp3",
|
|
"Clip.mov",
|
|
"Archive.zip",
|
|
"Budget.csv",
|
|
"Inter.ttf",
|
|
"Makefile",
|
|
];
|
|
let tile = |icon: FileIcon, name: &'static str| {
|
|
div()
|
|
.w(px(88.))
|
|
.flex()
|
|
.flex_col()
|
|
.items_center()
|
|
.gap_1p5()
|
|
.child(icon.size(IconSize::Xl))
|
|
.child(div().text_size(small).text_color(muted).child(name))
|
|
};
|
|
section(
|
|
"FileIcon",
|
|
"A file's icon by its name's extension: code, text, pictures, sound, video, archives, tables, fonts and scripts; a folder shut or open.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"files-icons",
|
|
div()
|
|
.w(px(640.))
|
|
.flex()
|
|
.flex_wrap()
|
|
.gap_y_4()
|
|
.child(tile(FileIcon::folder(false), "Folder"))
|
|
.child(tile(FileIcon::folder(true), "Open"))
|
|
.children(names.map(|name| tile(FileIcon::file(name), name))),
|
|
))
|
|
}
|
|
|
|
fn items(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let picked = keep("files-items", || SharedString::from("Brief.md"), window, cx);
|
|
let now = picked.read(cx).clone();
|
|
let pick = picked.clone();
|
|
let list =
|
|
entries()
|
|
.into_iter()
|
|
.take(5)
|
|
.fold(SelectableList::new("files-items"), |list, entry| {
|
|
let key = entry.name().clone();
|
|
list.row(
|
|
key.clone(),
|
|
FileItem::new(SharedString::from(format!("files-item-{key}")), entry).into(),
|
|
)
|
|
});
|
|
section(
|
|
"FileItem",
|
|
"A file or folder as a row: its icon and name over its size and when it changed.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"files-items",
|
|
div()
|
|
.w(px(420.))
|
|
.child(list.selected([now]).on_change(move |keys, _, cx| {
|
|
if let Some(key) = keys.first() {
|
|
change(&pick, cx, |now| *now = key.clone())
|
|
}
|
|
})),
|
|
))
|
|
}
|
|
|
|
fn listing(cx: &mut App) -> impl IntoElement + use<> {
|
|
section(
|
|
"FileList",
|
|
"A folder's entries in columns, as lists::DirectoryListing draws them: sort by a column, climb the path, open with Enter or a double press.",
|
|
cx,
|
|
)
|
|
.child(probe(
|
|
"files-listing",
|
|
div().w(px(640.)).h(px(360.)).child(
|
|
DirectoryListing::new("files-listing", ["Home", "Projects", "Atrium"], entries())
|
|
.on_open(|entry, _, _| log::info!("gallery: open {}", entry.name()))
|
|
.on_climb(|level, _, _| log::info!("gallery: climb to {level}")),
|
|
),
|
|
))
|
|
}
|
|
|
|
fn grid(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
|
let picked = keep("files-grid", || None::<SharedString>, window, cx);
|
|
let now = picked.read(cx).clone();
|
|
let pick = picked.clone();
|
|
let grid = FileGrid::new("files-grid", entries())
|
|
.on_select(move |name, _, cx| change(&pick, cx, |now| *now = Some(name.clone())))
|
|
.on_open(|entry, _, _| log::info!("gallery: open {}", entry.name()));
|
|
let grid = match now {
|
|
Some(name) => grid.selected(name),
|
|
None => grid,
|
|
};
|
|
section(
|
|
"FileGrid",
|
|
"Files and folders as tiles. A press picks one and a double press opens it; with focus the arrows move by tile and by row, and Enter opens.",
|
|
cx,
|
|
)
|
|
.child(probe("files-grid", div().w(px(640.)).child(grid)))
|
|
}
|
|
|
|
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
|
div()
|
|
.child(icons(cx))
|
|
.child(items(window, cx))
|
|
.child(listing(cx))
|
|
.child(grid(window, cx))
|
|
.child(explorer::explorer(window, cx))
|
|
.child(explorer::tree(cx))
|
|
.child(actions::context_menu(cx))
|
|
.child(actions::rename(window, cx))
|
|
.child(actions::drop_files(window, cx))
|
|
.child(find::previews(window, cx))
|
|
.child(find::recent(cx))
|
|
.child(find::favorites(cx))
|
|
.child(find::search(window, cx))
|
|
.child(find::duplicates(window, cx))
|
|
.child(find::archive(cx))
|
|
.child(find::hex(window, cx))
|
|
.child(transfers::operations(window, cx))
|
|
.child(transfers::queue(window, cx))
|
|
.child(transfers::downloads(window, cx))
|
|
.child(transfers::storage(cx))
|
|
.into_any_element()
|
|
}
|