mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-12 00:40:36 -06:00
M7: recent files as thumbnail cards on the home screen (#249)
The Recents section shows recent files as cards: the picture each file
carries (thumbnail.png in .figcraft and .fig, an image file itself) fitted
on its first page's colour, a divider, and a footer with the file's icon,
name and "Edited ... ago" (or "File not found"); the full path on hover.
Pictures are read off the frame's thread, only for cards in view, cached
by path and modification time, capped in bytes and pixels; missing,
damaged or oversized ones give a placeholder. They fade in, and cards
show a skeleton until their file has been looked at.
A sort menu (Alphabetical, Date created, Last modified; either order) and
a grid/list toggle sit above them. The list is a Name, Last modified and
Created table whose headers sort. View (per section) and sort are kept in
the preferences and set with ui.set {homeRecentsView, homeRecentsSort,
homeRecentsOrder}. A click selects, a double-click or Enter opens, arrow
keys move the selection; a right-click offers Open, Show in folder and
Remove from recents (file.openRecent {remove}).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
bflatastic
parent
b34c5de2ab
commit
bcedfeba10
@@ -37,7 +37,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
spec!(
|
||||
"file.openRecent",
|
||||
"Open recent",
|
||||
"{path? | index? (into the list), clear? (forget them all)} opens a recent file; a file that no longer exists is dropped from the list. Returns the list, newest first: {files: [{path, name, exists}]} (opening returns what `file.open` does)",
|
||||
"{path? | index? (into the list), clear? (forget them all), remove? (a path: forget that one)} opens a recent file; a file that no longer exists is dropped from the list. Returns the list, newest first: {files: [{path, name, exists}]} (opening returns what `file.open` does)",
|
||||
false,
|
||||
open_recent
|
||||
),
|
||||
@@ -625,6 +625,9 @@ fn open_recent(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if p.get("clear").and_then(Value::as_bool) == Some(true) {
|
||||
s.prefs.recent_files.clear();
|
||||
}
|
||||
if let Some(gone) = string(p, "remove") {
|
||||
s.prefs.recent_files.retain(|r| *r != gone);
|
||||
}
|
||||
let path = match (string(p, "path"), num(p, "index")) {
|
||||
(Some(path), _) => Some(path),
|
||||
(None, Some(i)) => Some(s.prefs.recent_files.get(i.max(0.0) as usize).cloned().ok_or_else(|| bad(CMD, "no such recent file"))?),
|
||||
|
||||
@@ -591,6 +591,14 @@ pub struct Prefs {
|
||||
/// The Getting started tips on the home screen were closed (Help ▸ Getting started shows them
|
||||
/// again).
|
||||
pub getting_started_dismissed: bool,
|
||||
/// How each home screen section shows its files, by section id (`"recents"`): `"grid"` (cards
|
||||
/// with the picture each file carries) or `"list"` (a table). Missing: the grid.
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub home_views: BTreeMap<String, String>,
|
||||
/// What the home screen's recent files are ordered by: `"modified"`, `"name"` or `"created"`.
|
||||
pub home_sort: String,
|
||||
/// Which way: `"desc"` (newest first, Z to A) or `"asc"`.
|
||||
pub home_order: String,
|
||||
}
|
||||
|
||||
impl Default for Prefs {
|
||||
@@ -616,6 +624,9 @@ impl Default for Prefs {
|
||||
recent_files: Vec::new(),
|
||||
online_files: Vec::new(),
|
||||
getting_started_dismissed: false,
|
||||
home_views: BTreeMap::new(),
|
||||
home_sort: "modified".into(),
|
||||
home_order: "desc".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,19 @@ fn open_recent_lists_opens_prunes_and_clears() {
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_recent_file_is_forgotten_and_nothing_opens() {
|
||||
let mut s = Session::empty();
|
||||
s.prefs.recent_files = vec!["/tmp/a.figcraft".into(), "/tmp/b.fig".into(), "/tmp/c.svg".into()];
|
||||
let list = s.execute("file.openRecent", &json!({"remove": "/tmp/b.fig"})).unwrap();
|
||||
assert_eq!(s.prefs.recent_files, vec!["/tmp/a.figcraft".to_string(), "/tmp/c.svg".to_string()]);
|
||||
assert_eq!(list["files"].as_array().map(Vec::len), Some(2), "the list as it is now");
|
||||
assert!(s.docs.is_empty(), "nothing opens");
|
||||
// A path that isn't in the list changes nothing.
|
||||
s.execute("file.openRecent", &json!({"remove": "/tmp/zzz.fig"})).unwrap();
|
||||
assert_eq!(s.prefs.recent_files.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recent_files_are_capped() {
|
||||
let dir = std::env::temp_dir().join(format!("figcraft-recent-cap-{}", std::process::id()));
|
||||
|
||||
Reference in New Issue
Block a user