mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Library panels: Locate Library… reads a missing library whose panel isn't persistent
ui.libraryPanel.locate decided whether a panel's library was missing through missing_file, which answers only for a persistent panel. A command that turns Persistent off leaves the panel open on its missing library until the next drawn frame closes it, and while the window is hidden the app draws no frame between control requests. Locate Library… on that panel answered "isn't missing its library" and read no file. The check now asks whether the panel's library can be found, the lookup the tab label and the per-frame tidy use. A panel whose library is there is still refused, persistent or not. window.swatchLibrary and window.graphicStyleLibrary had the same check: given the library id of such a panel, they answered "no library". They now bring the open panel forward and answer its file's name with count 0; the panel still closes at the next tidy. Docs: docs/mcp.md (ui.libraryPanel.locate, window.swatchLibrary).
This commit is contained in:
@@ -244,9 +244,17 @@ fn missing_file(app: &VectorcraftApp, k: Kind, id: &str) -> Option<String> {
|
||||
(k.name)(app, id).is_none().then(|| s.path.clone().unwrap_or_else(|| id.to_string()))
|
||||
}
|
||||
|
||||
/// The name of missing library `id` of `k` ([`missing_file`]): its file's name without the extension.
|
||||
/// The file of library `id` of `k` when its panel is open and the library can't be found,
|
||||
/// Persistent or not (a panel whose Persistent a command turned off stays open until [`tidy`]).
|
||||
fn open_missing_file(app: &VectorcraftApp, k: Kind, id: &str) -> Option<String> {
|
||||
let s = settings(&app.ui, k.kind, id).filter(|_| is_open(&app.ui, &panel_id(k.kind, id)))?;
|
||||
(k.name)(app, id).is_none().then(|| s.path.clone().unwrap_or_else(|| id.to_string()))
|
||||
}
|
||||
|
||||
/// The name of missing library `id` of `k` ([`missing_file`], or [`open_missing_file`]): its file's
|
||||
/// name without the extension.
|
||||
fn missing_name(app: &VectorcraftApp, k: Kind, id: &str) -> Option<String> {
|
||||
let file = missing_file(app, k, id)?;
|
||||
let file = missing_file(app, k, id).or_else(|| open_missing_file(app, k, id))?;
|
||||
Some(std::path::Path::new(&file).file_stem().map_or_else(|| file.clone(), |s| s.to_string_lossy().into_owned()))
|
||||
}
|
||||
|
||||
@@ -393,9 +401,10 @@ pub(crate) fn library_name<'a>(id: &str, name: &'a str) -> &'a str {
|
||||
}
|
||||
|
||||
/// The opener of library `K`'s libraries (`window.swatchLibrary {library}`): open library `library`
|
||||
/// (an id or a name, or the id of a missing library's persistent panel, [`missing_file`]) in its
|
||||
/// own panel, floating, or bring its panel forward when it is open; `null` closes every panel of
|
||||
/// `K`'s libraries. → {open, panel, name, count} (a missing library: its [`missing_name`], count 0)
|
||||
/// (an id or a name, or the id of a missing library's persistent or open panel, [`missing_name`])
|
||||
/// in its own panel, floating, or bring its panel forward when it is open; `null` closes every
|
||||
/// panel of `K`'s libraries. → {open, panel, name, count} (a missing library: its
|
||||
/// [`missing_name`], count 0)
|
||||
pub(crate) fn open_command<K: LibraryKind>(app: &mut VectorcraftApp, p: &Value) -> Result<Value, String> {
|
||||
let Some(key) = p.get("library").filter(|v| !v.is_null()) else {
|
||||
for panel in open_panels(&app.ui).into_iter().filter(|p| parse(p).is_some_and(|(k, _)| k.kind == K::KIND)) {
|
||||
@@ -488,8 +497,8 @@ pub(crate) fn command(app: &mut VectorcraftApp, p: &Value) -> Result<Value, Stri
|
||||
}))
|
||||
}
|
||||
|
||||
/// `ui.libraryPanel.locate {panel, path?}`: Locate Library… of a library panel whose library is
|
||||
/// missing ([`missing_file`]; any other panel is an error). Load the library at `path`, else one
|
||||
/// `ui.libraryPanel.locate {panel, path?}`: Locate Library… of a library panel whose library can't
|
||||
/// be found, Persistent or not (any other panel is an error). Load the library at `path`, else one
|
||||
/// picked in an open dialog (on the web the picked file arrives later and opens in a panel of its
|
||||
/// own, as Other Library…'s does), and show it in open panel `panel`, which keeps its view, Show
|
||||
/// Find Field and Persistent. When the file's library has a panel open already, `panel` closes and
|
||||
@@ -501,7 +510,7 @@ pub(crate) fn locate(app: &mut VectorcraftApp, p: &Value) -> Result<Value, Strin
|
||||
if !is_open(&app.ui, given) {
|
||||
return Err(format!("library panel `{given}` isn't open"));
|
||||
}
|
||||
if missing_file(app, k, id).is_none() {
|
||||
if (k.name)(app, id).is_some() {
|
||||
return Err(format!("library panel `{given}` isn't missing its library"));
|
||||
}
|
||||
let path = p.get("path").and_then(Value::as_str).map(str::to_string);
|
||||
@@ -1333,6 +1342,58 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locate_library_reads_a_missing_library_whose_panel_is_no_longer_persistent() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-locate-not-persistent-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(dir.join("moved")).unwrap();
|
||||
let palette = "GIMP Palette\nName: Brand\n#\n255 0 0\tRed\n";
|
||||
let gpl = dir.join("Brand.gpl");
|
||||
std::fs::write(&gpl, palette).unwrap();
|
||||
let mut app = app();
|
||||
let brand = panel_of(app.run("window.swatchLibrary.other", json!({"path": gpl.to_string_lossy()})).unwrap());
|
||||
app.run("ui.libraryPanel", json!({"panel": brand, "persistent": true})).unwrap();
|
||||
std::fs::remove_file(&gpl).unwrap();
|
||||
let mut next = restarted(&app);
|
||||
// After a command turns Persistent off, the panel stays open on its missing library until the next frame's tidy.
|
||||
next.run("ui.libraryPanel", json!({"panel": brand, "persistent": false})).unwrap();
|
||||
assert!(is_open(&next.ui, &brand));
|
||||
let moved = dir.join("moved").join("Brand.gpl");
|
||||
std::fs::write(&moved, palette).unwrap();
|
||||
let r = next.run("ui.libraryPanel.locate", json!({"panel": brand, "path": moved.to_string_lossy()})).unwrap();
|
||||
let located = panel_id("swatches", &format!("loaded/{}", moved.to_string_lossy()));
|
||||
assert_eq!((r["panel"].as_str(), r["persistent"].as_bool()), (Some(located.as_str()), Some(false)));
|
||||
assert!(!is_open(&next.ui, &brand) && is_open(&next.ui, &located), "{}", next.ui.status);
|
||||
let (_, id) = parse(&located).unwrap();
|
||||
assert!(SwatchLibraries::get(&next, id).is_some_and(|(_, lib)| lib.swatch("Red").is_some()));
|
||||
tidy(&mut next);
|
||||
assert!(is_open(&next.ui, &located), "its library is there now");
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_open_panel_whose_library_is_missing_is_brought_forward_by_its_id_when_not_persistent() {
|
||||
let dir = std::env::temp_dir().join(format!("vc-open-not-persistent-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let gpl = dir.join("Brand.gpl");
|
||||
std::fs::write(&gpl, "GIMP Palette\nName: Brand\n#\n255 0 0\tRed\n").unwrap();
|
||||
let mut app = app();
|
||||
let brand = panel_of(app.run("window.swatchLibrary.other", json!({"path": gpl.to_string_lossy()})).unwrap());
|
||||
app.run("ui.libraryPanel", json!({"panel": brand, "persistent": true})).unwrap();
|
||||
std::fs::remove_file(&gpl).unwrap();
|
||||
let mut next = restarted(&app);
|
||||
// After a command turns Persistent off, the panel stays open on its missing library until the next frame's tidy.
|
||||
next.run("ui.libraryPanel", json!({"panel": brand, "persistent": false})).unwrap();
|
||||
let (_, gone) = parse(&brand).unwrap();
|
||||
let r = next.run("window.swatchLibrary", json!({"library": gone})).unwrap();
|
||||
assert_eq!((r["panel"].as_str(), r["name"].as_str(), r["count"].as_u64()), (Some(brand.as_str()), Some("Brand"), Some(0)));
|
||||
assert!(is_open(&next.ui, &brand));
|
||||
tidy(&mut next);
|
||||
assert!(!is_open(&next.ui, &brand), "a panel that isn't persistent still closes");
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn previous_and_next_show_the_neighbouring_library_in_the_same_panel() {
|
||||
let mut app = app();
|
||||
|
||||
+5
-4
@@ -1719,11 +1719,12 @@ under `ui.library_panels`.
|
||||
`ui.libraryPanel.locate {panel, path?}` is Locate Library…: it loads the library at `path` (without one, it
|
||||
asks for a file) as Other Library… does and shows it in the open panel `panel`, which keeps its view, find
|
||||
field and Persistent, and answers as `ui.libraryPanel` does. When the file's library already has a panel open,
|
||||
`panel` closes and is forgotten, and that panel is brought forward, made persistent when `panel` was. On a
|
||||
panel whose library isn't missing, it answers an error without asking for or loading a file, and the panel
|
||||
keeps its library.
|
||||
`panel` closes and is forgotten, and that panel is brought forward, made persistent when `panel` was. It
|
||||
runs on any open panel whose library is missing, with Persistent on or off. On a panel whose library isn't
|
||||
missing, it answers an error without asking for or loading a file, and the panel keeps its library.
|
||||
`window.swatchLibrary` and `window.graphicStyleLibrary` also take a missing library's id, so a closed missing
|
||||
panel opens again; they answer its file's name without the extension and `count: 0`.
|
||||
panel opens again, and an open panel whose library is missing is brought forward with Persistent on or off;
|
||||
they answer its file's name without the extension and `count: 0`.
|
||||
|
||||
```json
|
||||
{"name":"invoke_menu","arguments":{"command":"window.swatchLibrary","params":{"library":"pastels"}}}
|
||||
|
||||
Reference in New Issue
Block a user